Merge f-029-par-amd (4772f0cd9)

This commit is contained in:
Niko1221 committed 2026-10-30 18:39:27 +01:00
commit 16e08b50cd
12 files changed
+1025 -10

No files matched your search

+7
View File
@@ -1656,6 +1656,13 @@ if(STRATA_ENABLE_CUDA OR STRATA_ENABLE_HIP)
target_link_libraries(pinned_shared_test PRIVATE strata_core ${_strata_gpu_runtime_target})
add_test(NAME pinned_shared_test COMMAND pinned_shared_test)
endif()
if(WIN32) # the replicas' shared expert arena as a named section (src/core/pinned.cu): two arenas, a child process, the last handle
add_executable(pinned_shared_win_test src/core/pinned_shared_win_test.cpp)
target_include_directories(pinned_shared_win_test PRIVATE include)
target_link_libraries(pinned_shared_win_test PRIVATE strata_core ${_strata_gpu_runtime_target})
add_test(NAME pinned_shared_win_test COMMAND pinned_shared_win_test)
set_tests_properties(pinned_shared_win_test PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 180)
endif()
# Plan v0.3 P2: the n-gram table read straight from the SSD. The selftest needs only the CPU and a temp file.
add_executable(ple_reader_test src/ngram/ple_reader_test.cpp)
+3
View File
@@ -90,6 +90,9 @@ struct PinnedArena {
std::vector<uint64_t> slice_starts;
void* mapping_base = nullptr; ///< actual mapping start; differs from base when a shared-file header exists
uint64_t mapping_bytes = 0; ///< bytes to release from mapping_base
/// shared-file arena only: the load state word in the file's header (0: nobody has loaded it, a pid: that engine is
/// loading it, ~0: it is complete). Shared by every engine that maps the file; null for a private arena.
std::atomic<uint64_t>* shared_state = nullptr;
~PinnedArena();
PinnedArena(const PinnedArena&) = delete;
PinnedArena& operator=(const PinnedArena&) = delete;
+162
View File
@@ -18,6 +18,18 @@ conversation's next turn therefore goes back to its replica, until that replica
reading the prompt again is cheaper than waiting. What each replica holds is tracked as hashes of 64-token blocks of
the prompts and answers it served (the engine itself checks the real prefix, as it always does).
Host RAM. Every engine keeps its own copy of the experts in RAM (about 47 GiB for Qwen3.8-Flash-Next IQ3_S), so two
replicas need two copies and four need four. "replica_shared_arena": true (or STRATA_REPLICA_SHARED_ARENA=1; opt-in)
backs the experts' arena of all replicas with ONE shared memory object (the engine's --shared-expert-arena, MAP_SHARED):
the experts are in RAM once, whatever the number of replicas. On Linux the object is a memfd the server creates
(os.memfd_create): it is not a file on a mount, so the size of /dev/shm does not limit it; the engines open it as
/proc/<server pid>/fd/<n>. On Windows it is a named section (CreateFileMapping) the engines open by name. Without memfd (an old kernel, a sandbox that refuses it) a file in /dev/shm is used instead.
The pack hash in the header refuses another model; the first engine to start reads the experts into it and the others
wait for it and skip the read (a state word in the header: nobody / a pid reading / filled; an engine whose reader died
takes the read over); the server closes the memfd (or unlinks the file) when it ends - an engine that still maps it
keeps its pages, the kernel frees them with the last mapping - and a /dev/shm file whose server is gone is removed at
the next start. See shared_arena_plan.
CPU cores. On Linux the replicas get disjoint physical cores (serve/cpu_affinity.py, from PR #1436 by agorevski), so
the host threads of the engines (the expert workers) do not compete. "replica_cpus": false turns it off.
"""
@@ -26,6 +38,7 @@ from __future__ import annotations
import collections
import copy
import os
import sys
import threading
import time
@@ -81,6 +94,154 @@ def _cards(g) -> list[int]:
raise ReplicaConfigError(f"cards must be numbers, not {g!r}") from None
ARENA_PREFIX = "strata-replicas-"
_MEMFDS: dict = {} # the memfds this server created: path (/proc/<pid>/fd/<n>) -> fd
ARENA_MIN_RAM = 8 << 30 # a memfd arena: at least this much free RAM to try (the engine reserves the real size)
ARENA_MIN_FREE = 8 << 30 # /dev/shm must have at least this much free to try (the engine reserves the real size)
ARENA_INCOMPATIBLE = ("--mmap-experts", "--resident-experts", "--shared-expert-arena", "--resident-budget-gib")
def _pid_alive(pid: int) -> bool:
try:
os.kill(pid, 0)
except ProcessLookupError:
return False
except PermissionError:
return True
return True
def sweep_stale_arenas(shm: str = "/dev/shm", uid: int | None = None) -> list[str]:
"""Remove the arena files of servers that no longer run (a killed server cannot unlink its own). A file of a living
server, or of another user, is never touched; a mapping an engine still holds stays valid either way."""
if uid is None:
uid = os.getuid() if hasattr(os, "getuid") else 0
gone = []
try:
names = os.listdir(shm)
except OSError:
return gone
head = f"{ARENA_PREFIX}{uid}-"
for n in names:
if not (n.startswith(head) and n.endswith(".bin")):
continue
try:
pid = int(n[len(head):-4])
except ValueError:
continue
if _pid_alive(pid):
continue
try:
os.unlink(os.path.join(shm, n))
gone.append(n)
except OSError:
pass
return gone
def _free(path: str) -> int:
try:
st = os.statvfs(path)
return st.f_bavail * st.f_frsize
except OSError:
return 0
def _mem_available() -> int:
try:
with open("/proc/meminfo") as f:
for line in f:
if line.startswith("MemAvailable:"):
return int(line.split()[1]) * 1024
except (OSError, ValueError, IndexError):
pass
return 0
def shared_arena_plan(cfg: dict, n_replicas: int, *, shm: str = "/dev/shm", platform: str | None = None,
environ=None, free_bytes=None, use_memfd: bool | None = None) -> tuple[str | None, str]:
"""(the file for --shared-expert-arena or None, a line for the log). Opt-in: "replica_shared_arena": true (or
STRATA_REPLICA_SHARED_ARENA=1). Only on Linux (the engine's MAP_SHARED backing), for Qwen's arena (not DeepSeek,
not the mapped or budgeted RAM modes, which have no arena), and with room in /dev/shm. The engine checks the real
size and reserves it before it loads (a tmpfs that is too small is an error at start, not a bus error later)."""
environ = os.environ if environ is None else environ
platform = sys.platform if platform is None else platform
want = cfg.get("replica_shared_arena")
if want is None:
want = str(environ.get("STRATA_REPLICA_SHARED_ARENA", "")).strip().lower() in ("1", "true", "on", "yes")
if not want or n_replicas < 2:
return None, ""
if platform.startswith("win"):
# a named pagefile-backed section the engines open by name (src/core/pinned.cu); it lives while any engine holds it
name = f"Local{chr(92)}strata-replicas-{os.getpid()}"
return f"win:{name}", (f"one shared expert arena for {n_replicas} replicas (a Windows section, {name}): "
"the experts are in RAM once")
if not platform.startswith("linux"):
return None, "shared expert arena: Linux and Windows only; each replica keeps its own copy of the experts in RAM"
args = [str(a) for a in cfg.get("args") or []]
if isinstance(cfg.get("replicas"), list):
for e in cfg["replicas"]:
if isinstance(e, dict):
args += [str(a) for a in e.get("args") or []]
if cfg.get("arch") == "deepseek41" or any(a in args for a in ARENA_INCOMPATIBLE):
return None, "shared expert arena: this model/config has no private expert arena to share; each replica as before"
if use_memfd is None:
use_memfd = hasattr(os, "memfd_create") and str(cfg.get("replica_shared_arena")).lower() != "devshm"
if use_memfd:
avail = free_bytes if free_bytes is not None else _mem_available()
if avail < ARENA_MIN_RAM:
return None, (f"shared expert arena: only {avail / 2**30:.1f} GiB of RAM is free; each replica keeps its own "
"copy of the experts in RAM")
try:
fd = os.memfd_create("strata-replicas-arena", os.MFD_CLOEXEC)
except OSError:
fd = -1
if fd >= 0:
path = f"/proc/{os.getpid()}/fd/{fd}"
_MEMFDS[path] = fd
warn = ""
try: # the arena once plus about 7 GB of private RAM per engine (measured); a warning, never a refusal
from strata_control import replica_profile as _rp
q = _rp.quant_of(cfg)
if q:
need = _rp.ARENA_GB[q] + n_replicas * _rp.PER_REPLICA_GB
if avail / 2**30 < need + 4:
warn = (f" WARNING: about {need:.0f} GB are needed for {n_replicas} engines and {avail / 2**30:.0f} GB "
"are free: the last engine may not fit (fewer replicas, or the layer split, is safer)")
except Exception: # noqa: BLE001
pass
return path, (f"one shared expert arena for {n_replicas} replicas (memory object of the server, not limited by "
f"the size of /dev/shm): the experts are in RAM once.{warn}")
if not (os.path.isdir(shm) and os.access(shm, os.W_OK)):
return None, f"shared expert arena: {shm} is not usable; each replica keeps its own copy of the experts in RAM"
free = free_bytes if free_bytes is not None else _free(shm)
if free < ARENA_MIN_FREE:
return None, (f"shared expert arena: {shm} has only {free / 2**30:.1f} GiB free (a tmpfs is half the RAM by "
"default); each replica keeps its own copy of the experts in RAM")
sweep_stale_arenas(shm)
uid = os.getuid() if hasattr(os, "getuid") else 0
path = os.path.join(shm, f"{ARENA_PREFIX}{uid}-{os.getpid()}.bin")
return path, f"one shared expert arena for {n_replicas} replicas ({path}): the experts are in RAM once"
def remove_arena(path: str | None) -> None:
"""Close the memfd / unlink the arena file (the server's end, or a start that fell back): engines that map it keep
their pages."""
if not path or path.startswith("win:"): # (a Windows section goes with its last handle)
return
fd = _MEMFDS.pop(path, None)
if fd is not None:
try:
os.close(fd)
except OSError:
pass
return
try:
os.unlink(path)
except OSError:
pass
def _suffixed(path, i: int):
if not isinstance(path, str) or not path:
return path
@@ -337,6 +498,7 @@ class ReplicaEngine:
e.close()
except BaseException as x: # noqa: BLE001
err = err or x
remove_arena(self.__dict__.get("arena_file")) # (an engine that still maps it keeps its pages)
if err:
raise err
+44 -1
View File
@@ -344,6 +344,12 @@ ADMIT_MAX_WAIT_S = _timeout_env("STRATA_ADMIT_MAX_WAIT_S", 30.0) or 0.0
COALESCE_PREFILL = max(0, int(os.environ.get("STRATA_COALESCE_PREFILL") or 0))
COALESCE_TOKENS = max(256, int(os.environ.get("STRATA_COALESCE_TOKENS") or 16384)) # prompt tokens read together at most
COALESCE_WAIT_MS = max(0, int(os.environ.get("STRATA_COALESCE_WAIT_MS") or 0)) # a burst's later requests register within this long
# Adaptive: a read is shared (and the leader waits for its followers) only when this engine has seen at least
# COALESCE_MIN_REQS requests arrive within COALESCE_BURST_MS (the leader's own included). Fewer than that - one or two
# requests per engine, as 8 requests on 4 replicas - read plainly, with no wait: nothing to share, and the wait and the
# longer pass only cost (measured 4 x 1 replicas at 8 requests: 168 against 196 tok/s with an unconditional wait).
COALESCE_MIN_REQS = max(2, int(os.environ.get("STRATA_COALESCE_MIN_REQS") or 3))
COALESCE_BURST_MS = max(10, int(os.environ.get("STRATA_COALESCE_BURST_MS") or 250))
STALL_CPU_EPS_S = 0.5 # CPU seconds the engine may use in the whole stall window and still count as frozen
STALL_IO_EPS_B = 1 << 20 # bytes read or written in that window
@@ -1203,6 +1209,7 @@ class StrataEngine:
self.admitting = 0 # #1603: ... and those that gave the lines back to wait for a slot
self.par_counters = {"queued": 0, "queue_wait_s": 0.0}
self.wait_lens: list[list[int]] = [] # ... their prompt lengths (a long read gives way to short ones)
self.arrivals = collections.deque(maxlen=64) # monotonic times of the last requests that came for the control lines (STRATA_COALESCE_*)
self.ctl_epoch = 0 # how often the control lines were taken
self.ctl = threading.Lock() # one admission or solo request on the control lines at a time
self.gen = self.__dict__.get("gen", 0) + 1 # which engine process this is (a request notes its own)
@@ -1745,6 +1752,7 @@ class StrataEngine:
with self.slot_cv:
self.waiting += 1
self.wait_lens.append(entry)
self.arrivals.append(entry[1])
beat = time.monotonic()
try:
while True:
@@ -1812,6 +1820,16 @@ class StrataEngine:
same layer-major pass as the BGEN's prompt. Each follower finds its slot in cancel._strata_preslot."""
picked = []
total = len(lead_prompt)
def seen() -> int:
now = time.monotonic()
with self.slot_cv:
return max(sum(1 for t in self.arrivals if now - t <= COALESCE_BURST_MS / 1000.0), 1 + len(self.wait_lens))
recent = seen()
if recent < COALESCE_MIN_REQS and COALESCE_WAIT_MS > 0:
time.sleep(0.010) # (the first request of a burst holds the lines before its neighbours have arrived)
recent = seen()
if recent < COALESCE_MIN_REQS:
return # one or two requests on this engine: a plain read, at most 10 ms lost
if COALESCE_WAIT_MS > 0:
time.sleep(COALESCE_WAIT_MS / 1000.0) # the requests of a burst reach the server a few ms apart
with self.slot_cv:
@@ -8569,9 +8587,14 @@ def start_replicas(cfg: dict, groups: list[list[int]], exe: str, effort_end: lis
flush=True)
engines: list = [None] * n
errors: list = []
arena, arena_note = rp.shared_arena_plan(cfg, n) # opt-in: one copy of the experts in RAM for all replicas
if arena_note:
print(f"[strata] {arena_note}", flush=True)
def one(i: int):
rc = rp.replica_cfg(cfg, i, groups[i])
if arena:
rc["args"] = list(rc["args"]) + ["--shared-expert-arena", arena]
try:
engines[i] = StrataEngine(exe, engine_args(rc) + effort_end, cwd=rc.get("cwd"), log=rc.get("log"),
env=child_env(rc, offline=offline), lazy=lazy, cpus=cpus[i] if cpus else None)
@@ -8588,6 +8611,16 @@ def start_replicas(cfg: dict, groups: list[list[int]], exe: str, effort_end: lis
t.start()
for t in ts:
t.join()
if errors and arena and len(errors) < n:
# a replica that died while it was reading the experts into the shared arena (or any other one that failed) while
# another one is up: the living engine took the read over (the state word in the arena), so start the failed ones
# once more on the same arena instead of giving up the sharing
failed = sorted(i for i, _ in errors)
errors.clear()
print(f"[strata] replica(s) {', '.join(map(str, failed))} did not start; starting again on the shared expert arena",
flush=True)
for i in failed:
one(i)
if errors:
for e in engines:
if e is not None:
@@ -8595,10 +8628,20 @@ def start_replicas(cfg: dict, groups: list[list[int]], exe: str, effort_end: lis
e.close()
except Exception: # noqa: BLE001
pass
rp.remove_arena(arena)
i, e = errors[0]
if arena: # a shared arena that could not be made (no room in /dev/shm, a file of another pack): as before
print(f"[strata] replica {i} did not start with the shared expert arena ({e}); starting every replica with "
"its own copy of the experts instead", flush=True)
return start_replicas({**cfg, "replica_shared_arena": False}, groups, exe, effort_end, lazy, offline=offline)
raise SystemExit(f"[strata] replica {i} did not start: {e}")
load = int(cfg.get("replica_load_tokens") or rp.LOAD_TOKENS)
return rp.ReplicaEngine(engines, load_tokens=load)
eng = rp.ReplicaEngine(engines, load_tokens=load)
if arena:
eng.__dict__["arena_file"] = arena
import atexit
atexit.register(rp.remove_arena, arena) # (a killed server leaves the file: the next start sweeps it)
return eng
def start_split(cfg: dict, spec: dict, exe: str, effort_end: list[str], lazy: bool, *, offline: bool = False):
+126 -1
View File
@@ -11,7 +11,8 @@ from pathlib import Path
from unittest import mock
from serve.frontend import ChatTemplate
from serve.replicas import BLOCK, ReplicaConfigError, ReplicaEngine, Router, cpu_sets, gpu_groups, replica_cfg
from serve.replicas import (BLOCK, ReplicaConfigError, ReplicaEngine, Router, cpu_sets, gpu_groups, remove_arena,
replica_cfg, shared_arena_plan, sweep_stale_arenas)
from serve.server import ByteTokenizer, Service, StrataEngine, serve
from serve.test_parallel import FAKE_BATCH
@@ -309,5 +310,129 @@ class ReplicaService(unittest.TestCase):
self.assertEqual(self.served()[1], 3)
class SharedArena(unittest.TestCase):
"""The opt-in shared expert arena of the replicas (shared_arena_plan): when it is used, when it is not, what a stale
file means."""
CFG = {"replica_shared_arena": True, "args": ["--pack", "/p"], "gpu": [0, 1]}
def plan(self, cfg=None, n=2, tmp=None, **kw):
return shared_arena_plan(cfg or self.CFG, n, shm=tmp, platform=kw.pop("platform", "linux"),
environ=kw.pop("environ", {}), free_bytes=kw.pop("free_bytes", 100 << 30),
use_memfd=kw.pop("use_memfd", False), **kw)
def test_off_by_default_and_for_one_engine(self):
with tempfile.TemporaryDirectory() as d:
self.assertEqual(self.plan({"args": []}, tmp=d), (None, ""))
self.assertEqual(self.plan(n=1, tmp=d), (None, ""))
self.assertEqual(self.plan({"args": []}, tmp=d, environ={"STRATA_REPLICA_SHARED_ARENA": "0"}), (None, ""))
def test_on_by_config_or_environment(self):
with tempfile.TemporaryDirectory() as d:
path, note = self.plan(tmp=d)
self.assertTrue(path and path.startswith(d) and path.endswith(".bin"), path)
self.assertIn("once", note)
path2, _ = self.plan({"args": []}, tmp=d, environ={"STRATA_REPLICA_SHARED_ARENA": "1"})
self.assertTrue(path2)
self.assertEqual(path, path2) # (one server: one file)
def test_windows_gets_a_named_section(self):
with tempfile.TemporaryDirectory() as d:
path, note = self.plan(tmp=d, platform="win32")
self.assertTrue(path and path.startswith("win:Local") and "strata-replicas-" in path, path)
self.assertIn("Windows section", note)
remove_arena(path) # (nothing to remove: harmless)
def test_only_where_there_is_an_arena_to_share(self):
with tempfile.TemporaryDirectory() as d:
self.assertIsNone(self.plan(tmp=d, platform="darwin")[0])
for flag in ("--mmap-experts", "--resident-experts", "--resident-budget-gib", "--shared-expert-arena"):
self.assertIsNone(self.plan({**self.CFG, "args": ["--pack", "/p", flag]}, tmp=d)[0], flag)
self.assertIsNone(self.plan({**self.CFG, "replicas": [{"gpus": [0], "args": ["--mmap-experts"]}, {"gpus": [1]}]}, tmp=d)[0])
self.assertIsNone(self.plan({**self.CFG, "arch": "deepseek41"}, tmp=d)[0])
def test_no_room_or_no_shm_says_so_and_falls_back(self):
with tempfile.TemporaryDirectory() as d:
path, note = self.plan(tmp=d, free_bytes=2 << 30)
self.assertIsNone(path)
self.assertIn("2.0 GiB free", note)
path, note = self.plan(tmp=d + "/nope")
self.assertIsNone(path)
self.assertIn("not usable", note)
def test_a_stale_file_goes_a_live_one_stays(self):
with tempfile.TemporaryDirectory() as d:
uid = __import__("os").getuid() if hasattr(__import__("os"), "getuid") else 0
dead = Path(d) / f"strata-replicas-{uid}-2999999.bin"
live = Path(d) / f"strata-replicas-{uid}-{__import__('os').getpid()}.bin"
other = Path(d) / f"strata-replicas-{uid + 1}-2999999.bin"
junk = Path(d) / "strata-replicas-x.bin"
for f in (dead, live, other, junk):
f.write_bytes(b"x")
with mock.patch("serve.replicas._pid_alive", side_effect=lambda pid: pid != 2999999):
gone = sweep_stale_arenas(d, uid)
self.assertEqual(gone, [dead.name])
self.assertFalse(dead.exists())
self.assertTrue(live.exists() and other.exists() and junk.exists())
@unittest.skipUnless(hasattr(__import__("os"), "memfd_create") and sys.platform.startswith("linux"), "Linux memfd")
def test_a_memfd_is_not_limited_by_dev_shm_and_is_closed_at_the_end(self):
import os
path, note = shared_arena_plan(self.CFG, 2, platform="linux", environ={}, free_bytes=100 << 30, use_memfd=True)
self.assertTrue(path and path.startswith(f"/proc/{os.getpid()}/fd/"), path)
fd = int(path.rsplit("/", 1)[1])
os.ftruncate(fd, 1 << 20)
self.assertEqual(os.fstat(fd).st_size, 1 << 20)
with open(path, "r+b") as other: # (how an engine opens it)
other.seek(100)
other.write(b"x")
remove_arena(path)
with self.assertRaises(OSError):
os.fstat(fd)
remove_arena(path) # (twice: harmless)
@unittest.skipUnless(hasattr(__import__("os"), "memfd_create") and sys.platform.startswith("linux"), "Linux memfd")
def test_a_memfd_arena_warns_when_the_engines_will_not_all_fit(self):
cfg = {**self.CFG, "model_name": "qwen3.8-flash-next-iq3_s"}
path, note = shared_arena_plan(cfg, 2, platform="linux", environ={}, free_bytes=57 << 30, use_memfd=True)
try:
self.assertTrue(path)
self.assertIn("WARNING", note)
self.assertIn("may not fit", note)
finally:
remove_arena(path)
path, note = shared_arena_plan(cfg, 2, platform="linux", environ={}, free_bytes=120 << 30, use_memfd=True)
try:
self.assertNotIn("WARNING", note)
finally:
remove_arena(path)
def test_a_memfd_needs_free_ram(self):
path, note = shared_arena_plan(self.CFG, 2, platform="linux", environ={}, free_bytes=2 << 30, use_memfd=True)
self.assertIsNone(path)
self.assertIn("2.0 GiB of RAM", note)
def test_removing_twice_or_nothing_is_harmless(self):
with tempfile.TemporaryDirectory() as d:
f = Path(d) / "a.bin"
f.write_bytes(b"x")
remove_arena(str(f))
remove_arena(str(f))
remove_arena(None)
self.assertFalse(f.exists())
def test_the_replica_engine_unlinks_the_file_at_close(self):
with tempfile.TemporaryDirectory() as d:
f = Path(d) / "a.bin"
f.write_bytes(b"x")
class E:
def close(self):
pass
r = ReplicaEngine([E(), E()])
r.__dict__["arena_file"] = str(f)
r.close()
self.assertFalse(f.exists())
if __name__ == "__main__":
unittest.main()
+21 -1
View File
@@ -8953,6 +8953,11 @@ def main() -> int:
help="several GPUs: run N independent engines, each on its own equal group of the chosen cards (2 "
"replicas on 4 cards = two 2-card engines), requests spread over them (opt-in; more total "
"throughput for a model that fits fewer cards; each replica holds its own copy of the model)")
ap.add_argument("--replica-profile", action="store_true", default=False,
help="two or more GPUs, Qwen3.8 IQ3_S/IQ3_XXS/Q2_0: one engine per card behind the server instead of one "
"layer split, the experts shared in RAM, the batch switches on (opt-in; about 1.8 times the "
"throughput at 8-16 requests at once on 4 Radeon AI PRO R9700; needs about 61 GB of RAM for 2 "
"engines and 66 GB for 4; a layer split you chose, replicas or --parallel always win)")
ap.add_argument("--kv-streaming", choices=["auto", "on", "off"], default="auto",
help="from a 64K context: keep the KV cache in RAM and only the attention's window in VRAM (more "
"experts fit on the GPU); auto: when the RAM has room for it")
@@ -10095,7 +10100,22 @@ def main() -> int:
cfg["gpu_order"] = "as_given"
ok(f"layer split across GPUs {multi} ({cfg['layer_split']})")
recommend_remote_expert_opt(cfg, off=a.no_remote_expert_opt)
if hip and a.layer_split in (None, "auto"): # four Radeon AI PRO R9700 cards and Qwen3.8 IQ3_S: the measured serving profile
replica_done = False
if a.replicas is None and a.layer_split in (None, "auto") and (getattr(a, "replica_profile", False) or
os.environ.get("STRATA_REPLICA_PROFILE")):
try: # the opt-in replica profile (strata_control/replica_profile.py)
from strata_control import replica_profile
if getattr(a, "replica_profile", False):
cfg["replica_profile"] = True
lines = replica_profile.apply([{"name": c.get("name"), "vram_gb": c.get("vram_gb"), "vendor": c.get("vendor"),
"index": c.get("index")} for c in chosen], cfg, float(ram),
parallel_given=a.parallel is not None)
replica_done = bool(cfg.get("replicas"))
for line in lines:
(ok if replica_done else say)(line)
except Exception as e: # noqa: BLE001 - a profile is never a reason to stop setup
say(f" (the replica profile was not applied: {e})")
if hip and a.layer_split in (None, "auto") and not replica_done: # four Radeon AI PRO R9700 cards and Qwen3.8 IQ3_S: the measured serving profile
try: # (strata_control/amd_four_card.py); what the person chose always wins
from strata_control import amd_four_card
for line in amd_four_card.apply([{"name": c.get("name"), "vram_gb": c.get("vram_gb"), "vendor": "amd"} for c in chosen],
+71 -1
View File
@@ -20,6 +20,10 @@
#include "strata/kernels/cpu/pool.hpp"
#include <cuda_runtime.h>
#ifndef _WIN32
#include <signal.h>
#include <unistd.h>
#endif
#include <algorithm>
#include <atomic>
@@ -72,6 +76,27 @@
#endif
#endif
// The pid of this process and whether a pid still exists (the shared arena's reader election, both OSes)
static uint64_t arena_this_pid() {
#ifdef _WIN32
return (uint64_t) GetCurrentProcessId();
#else
return (uint64_t) getpid();
#endif
}
static bool arena_pid_alive(uint64_t pid) {
#ifdef _WIN32
HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, (DWORD) pid);
if (h == nullptr) return GetLastError() == ERROR_ACCESS_DENIED; // (denied: it exists; invalid parameter: it does not)
DWORD code = 0;
const bool alive = GetExitCodeProcess(h, &code) && code == STILL_ACTIVE;
CloseHandle(h);
return alive;
#else
return !(kill((pid_t) pid, 0) != 0 && errno == ESRCH);
#endif
}
namespace strata::core {
namespace detail {
@@ -6000,6 +6025,43 @@ bool ArenaExpertSource::open(const std::string& pack_dir, int64_t n_layers, int6
const bool unbuffered = experts_unbuffered(files, want + (uint64_t) blob, why);
const int readers = unbuffered ? std::max(threads, 16) : threads; // 16 keep a PCIe 5 drive's queue full
LoadStats st;
// A shared arena (--shared-expert-arena, the replicas): ONE engine reads the experts into it, the others wait and skip the
// read. The state word in the file's header says who: 0 nobody, a pid loading, ~0 filled. An engine that finds a
// pid that is gone takes the load over (the previous one died mid-read: the arena is read again from the start), so a
// dead loader never leaves the others waiting, and an engine that waits too long loads it itself.
constexpr uint64_t kArenaFilled = ~uint64_t{0};
std::atomic<uint64_t>* arena_state = padded_ ? nullptr : a->shared_state;
bool arena_leader = false, arena_skip = false;
if (arena_state != nullptr) {
const uint64_t me = arena_this_pid();
const auto t_begin = std::chrono::steady_clock::now();
bool said = false;
for (;;) {
uint64_t cur = arena_state->load(std::memory_order_acquire);
if (cur == kArenaFilled) { arena_skip = true; break; }
if (cur == 0 || cur == me) {
if (arena_state->compare_exchange_strong(cur, me, std::memory_order_acq_rel)) { arena_leader = true; break; }
continue;
}
const bool alive = arena_pid_alive(cur);
if (!alive) {
if (arena_state->compare_exchange_strong(cur, me, std::memory_order_acq_rel)) {
std::fprintf(stderr, "strata generate: shared expert arena: engine %llu died while reading it; reading it again\n",
(unsigned long long) cur);
arena_leader = true;
break;
}
continue;
}
if (std::chrono::steady_clock::now() - t_begin > std::chrono::minutes(45)) break; // (loads it on its own)
if (!said) {
said = true;
std::fprintf(stderr, "strata generate: shared expert arena: engine %llu is reading the experts; waiting for it "
"instead of reading them again\n", (unsigned long long) cur);
}
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
}
std::atomic<int> ready{0};
std::thread reg;
if (deferred) {
@@ -6011,7 +6073,12 @@ bool ArenaExpertSource::open(const std::string& pack_dir, int64_t n_layers, int6
});
}
const std::atomic<int>* rp = deferred ? &ready : nullptr;
if (from_gguf) {
if (arena_skip) {
st.ok = true;
st.bytes = want;
st.seconds = 0.0;
std::fprintf(stderr, "strata generate: shared expert arena: already filled by another engine; the read is skipped\n");
} else if (from_gguf) {
st = load_experts_gguf(gguf_, a->data(), lay, readers, unbuffered, rp, padded_ ? &pad_ : nullptr);
} else {
if (unbuffered && !padded_) st = load_experts_direct(path, a->data(), loff, lbytes, readers, /*chunk=*/8u << 20, rp);
@@ -6022,15 +6089,18 @@ bool ArenaExpertSource::open(const std::string& pack_dir, int64_t n_layers, int6
std::fprintf(stderr, "strata generate: expert arena read %s (%s)\n", unbuffered ? "unbuffered" : "through the file cache",
why.c_str());
if (!st.ok) {
if (arena_leader) arena_state->store(0, std::memory_order_release); // (somebody else may try)
delete a;
err = "ArenaExpertSource: the expert load was refused: " + (st.error.empty() ? std::string("unknown") : st.error);
return false;
}
if (st.bytes != want) {
if (arena_leader) arena_state->store(0, std::memory_order_release);
delete a;
err = "ArenaExpertSource: the load read " + std::to_string(st.bytes) + " B of " + std::to_string(want);
return false;
}
if (arena_leader) arena_state->store(kArenaFilled, std::memory_order_release); // the others may use (and skip reading) it
if (std::getenv("STRATA_VERIFY_ARENA") != nullptr) { // tests: the loaded arena's checksum, any loader
std::fprintf(stderr, "strata generate: expert arena checksum %016llx (%.2f GiB)\n",
(unsigned long long) fnv1a64(a->data(), want), (double) want / (1ull << 30));
+214 -3
View File
@@ -2,6 +2,7 @@
#include "strata/core/pinned.hpp"
#include "strata/platform/memory.hpp"
#include "strata/core/pin_budget.hpp"
#include <cstddef>
#include <cuda_runtime.h>
@@ -32,6 +33,7 @@
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <unistd.h>
#include <linux/mman.h>
#ifndef MAP_HUGE_2MB
@@ -56,6 +58,51 @@ struct SharedArenaHeader {
};
static_assert(sizeof(SharedArenaHeader) <= kSharedArenaHeaderBytes);
#ifdef _WIN32
// Shared-section views: MapViewOfFile address -> the section handle, so release() unmaps and closes instead of VirtualFree.
std::mutex g_views_mu;
std::map<void*, HANDLE> g_views;
#endif
#ifndef _WIN32
// MemAvailable and, in a cgroup v2 group with a limit, the room left under it; ~0 when neither can be read
uint64_t free_ram_room() {
uint64_t room = ~uint64_t{0};
if (std::FILE* f = std::fopen("/proc/meminfo", "r")) {
char line[256];
while (std::fgets(line, sizeof line, f) != nullptr) {
unsigned long long kb = 0;
if (std::sscanf(line, "MemAvailable: %llu kB", &kb) == 1) { room = (uint64_t) kb * 1024u; break; }
}
std::fclose(f);
}
std::string cg;
if (std::FILE* f = std::fopen("/proc/self/cgroup", "r")) {
char line[512];
while (std::fgets(line, sizeof line, f) != nullptr)
if (std::strncmp(line, "0::", 3) == 0) { cg = line + 3; break; }
std::fclose(f);
}
while (!cg.empty() && (cg.back() == '\n' || cg.back() == '\r')) cg.pop_back();
if (!cg.empty()) {
unsigned long long lim = 0, cur = 0;
bool have_lim = false;
if (std::FILE* f = std::fopen(("/sys/fs/cgroup" + cg + "/memory.max").c_str(), "r")) {
have_lim = std::fscanf(f, "%llu", &lim) == 1; // ("max" does not parse: no limit)
std::fclose(f);
}
if (have_lim) {
if (std::FILE* f = std::fopen(("/sys/fs/cgroup" + cg + "/memory.current").c_str(), "r")) {
if (std::fscanf(f, "%llu", &cur) != 1) cur = 0;
std::fclose(f);
}
room = std::min<uint64_t>(room, lim > cur ? (uint64_t) (lim - cur) : 0);
}
}
return room;
}
#endif
#ifndef _WIN32
// bytes in free blocks of 2 MiB or more (/proc/buddyinfo, order >= 9): what MADV_HUGEPAGE can get without compaction
uint64_t free_huge_bytes() {
@@ -96,8 +143,102 @@ void* reserve(uint64_t bytes, PageBacking& got, std::string& note, const std::st
#ifdef _WIN32
if (!shared_file.empty()) {
note = "shared-file arena backing is not implemented on Windows";
return nullptr;
// The shared arena on Windows is a named, pagefile-backed section (CreateFileMappingW) that every engine of the
// replicas opens by name: "win:Local\\strata-replicas-<server pid>" (a name without the prefix is used as it is).
// The first engine creates it at the full size, the others open it; it lives while any process holds a handle or
// a view, so a replica that restarts joins the same pages. The first 4 KiB are the same header as on Linux (the
// pack hash, the size, the load state word); a new section is all zeros, so "no magic" means "nobody has been
// here" - two engines that both find it empty write the same header.
if (shared_pack_hash == 0) {
note = "shared arena requires a nonzero pack hash";
return nullptr;
}
if (bytes > UINT64_MAX - kSharedArenaHeaderBytes) {
note = "shared arena size overflows its header";
return nullptr;
}
const uint64_t file_bytes = bytes + kSharedArenaHeaderBytes;
std::string nm = shared_file.rfind("win:", 0) == 0 ? shared_file.substr(4) : shared_file;
std::wstring wname(nm.begin(), nm.end());
// the section is committed memory (RAM plus pagefile) from the moment it exists: say so before taking it
MEMORYSTATUSEX ms{};
ms.dwLength = sizeof ms;
if (GlobalMemoryStatusEx(&ms) && file_bytes + (2ull << 30) > ms.ullAvailPageFile) {
HANDLE probe = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, wname.c_str()); // (already there: nothing new is committed)
if (probe == nullptr) {
note = "not enough free memory (RAM + pagefile) for the shared arena " + nm + ": it needs " +
std::to_string((unsigned long long) (file_bytes >> 20)) + " MiB and " +
std::to_string((unsigned long long) (ms.ullAvailPageFile >> 20)) + " MiB are available (2 GiB are kept back)";
return nullptr;
}
CloseHandle(probe);
}
HANDLE h = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, (DWORD) (file_bytes >> 32),
(DWORD) (file_bytes & 0xffffffffu), wname.c_str());
if (h == nullptr) {
note = "cannot create shared arena section " + nm + ": Windows error " + std::to_string((unsigned long long) GetLastError());
return nullptr;
}
void* view = MapViewOfFile(h, FILE_MAP_ALL_ACCESS, 0, 0, 0); // (0: the whole section, as big as it was created)
if (view == nullptr) {
const DWORD e = GetLastError();
CloseHandle(h);
note = "cannot map shared arena section " + nm + ": Windows error " + std::to_string((unsigned long long) e);
return nullptr;
}
MEMORY_BASIC_INFORMATION mbi{};
if (VirtualQuery(view, &mbi, sizeof mbi) == 0 || (uint64_t) mbi.RegionSize < file_bytes) {
UnmapViewOfFile(view);
CloseHandle(h);
note = "shared arena section " + nm + " is smaller than the " + std::to_string((unsigned long long) file_bytes) +
" B this engine needs (including its header)";
return nullptr;
}
auto* hdr = static_cast<SharedArenaHeader*>(view);
bool empty = true;
for (char c : hdr->magic) empty = empty && c == 0;
if (empty) {
SharedArenaHeader fresh{};
std::memcpy(fresh.magic, kSharedArenaMagic, sizeof fresh.magic);
fresh.version = 1;
fresh.header_bytes = (uint32_t) kSharedArenaHeaderBytes;
fresh.arena_bytes = bytes;
fresh.pack_hash = shared_pack_hash;
// fields after the magic first, the magic last: an engine that sees the magic sees a complete header (the
// state word, reserved[0], stays zero)
std::memcpy(reinterpret_cast<char*>(hdr) + sizeof fresh.magic, reinterpret_cast<char*>(&fresh) + sizeof fresh.magic,
sizeof fresh - sizeof fresh.magic);
MemoryBarrier();
std::memcpy(hdr->magic, fresh.magic, sizeof fresh.magic);
} else {
// (an engine that arrives in the instant between another one's header fields and its magic never sees a
// half header: the magic is written last)
if (std::memcmp(hdr->magic, kSharedArenaMagic, sizeof hdr->magic) != 0 || hdr->version != 1 ||
hdr->header_bytes != kSharedArenaHeaderBytes || hdr->arena_bytes != bytes) {
UnmapViewOfFile(view);
CloseHandle(h);
note = "shared arena section " + nm + " has an incompatible or missing header";
return nullptr;
}
if (hdr->pack_hash != shared_pack_hash) {
char b[256];
std::snprintf(b, sizeof b, "shared arena %s was written for pack hash %016llx, expected %016llx", nm.c_str(),
(unsigned long long) hdr->pack_hash, (unsigned long long) shared_pack_hash);
UnmapViewOfFile(view);
CloseHandle(h);
note = b;
return nullptr;
}
}
{
std::lock_guard<std::mutex> lk(g_views_mu);
g_views[view] = h;
}
mapping_base = view;
mapping_bytes = file_bytes;
got = PageBacking::NormalPages;
note = "shared section " + nm + " (pack hash checked)";
return static_cast<uint8_t*>(view) + kSharedArenaHeaderBytes;
}
// MEM_LARGE_PAGES needs SeLockMemoryPrivilege. Having it assigned to the account is not enough: the
// PROCESS must enable it in its own token (AdjustTokenPrivileges) before VirtualAlloc, or the call fails.
@@ -200,7 +341,27 @@ void* reserve(uint64_t bytes, PageBacking& got, std::string& note, const std::st
return nullptr;
}
SharedArenaHeader hdr{};
const ssize_t got_header = pread(fd, &hdr, sizeof hdr, 0);
ssize_t got_header = pread(fd, &hdr, sizeof hdr, 0);
// the creator sizes the file first and writes the header right after: an engine that starts in that gap waits
// for it (up to 10 s) instead of calling the file broken
for (int w = 0; w < 100 && got_header == (ssize_t) sizeof hdr &&
std::memcmp(hdr.magic, kSharedArenaMagic, sizeof hdr.magic) != 0 &&
hdr.magic[0] == 0 && hdr.magic[1] == 0; ++w) {
usleep(100 * 1000);
got_header = pread(fd, &hdr, sizeof hdr, 0);
}
if (got_header == (ssize_t) sizeof hdr && std::memcmp(hdr.magic, kSharedArenaMagic, sizeof hdr.magic) != 0 &&
hdr.magic[0] == 0 && hdr.magic[1] == 0) {
// still no header after 10 s: the engine that sized the file died before writing it. Every engine
// fills the arena itself, so nothing in the file is worth keeping: write the header as the creator would
std::memset(&hdr, 0, sizeof hdr);
std::memcpy(hdr.magic, kSharedArenaMagic, sizeof hdr.magic);
hdr.version = 1;
hdr.header_bytes = (uint32_t) kSharedArenaHeaderBytes;
hdr.arena_bytes = bytes;
hdr.pack_hash = shared_pack_hash;
got_header = pwrite(fd, &hdr, sizeof hdr, 0);
}
if (got_header != (ssize_t) sizeof hdr ||
std::memcmp(hdr.magic, kSharedArenaMagic, sizeof hdr.magic) != 0 ||
hdr.version != 1 || hdr.header_bytes != kSharedArenaHeaderBytes || hdr.arena_bytes != bytes) {
@@ -220,6 +381,36 @@ void* reserve(uint64_t bytes, PageBacking& got, std::string& note, const std::st
}
}
// tmpfs: take the pages now (ftruncate only sets the length). With /dev/shm too small the load would die with a bus
// error part-way; here a full tmpfs is a plain error at start, for every engine that maps the file (pages already
// taken cost nothing), and the server then starts its replicas with private copies. A file on a disk stays sparse.
{
struct statfs sf{};
if (fstatfs(fd, &sf) == 0 && (unsigned long) sf.f_type == 0x01021994ul) {
// a memfd (or any tmpfs file not capped by a mount size) would take the RAM and then the OOM killer: check the
// free RAM and the cgroup's room for the pages that are not there yet before taking them
struct stat st2{};
const uint64_t have = fstat(fd, &st2) == 0 ? (uint64_t) st2.st_blocks * 512u : 0;
const uint64_t need = file_bytes > have ? file_bytes - have : 0;
if (need > 0) {
const uint64_t room = free_ram_room();
if (room != ~uint64_t{0} && need + (2ull << 30) > room) {
close(fd);
note = "not enough free RAM for the shared arena " + shared_file + ": it needs " +
std::to_string((unsigned long long) (need >> 20)) + " MiB more and " +
std::to_string((unsigned long long) (room >> 20)) + " MiB are free (2 GiB are kept back)";
return nullptr;
}
}
const int fe = posix_fallocate(fd, 0, (off_t) file_bytes);
if (fe != 0 && fe != EOPNOTSUPP && fe != EINVAL) {
close(fd);
note = "not enough room for the shared arena " + shared_file + " (" +
std::to_string((unsigned long long) (file_bytes >> 20)) + " MiB): " + std::strerror(fe);
return nullptr;
}
}
}
void* map = mmap(nullptr, (size_t) file_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
const int e = errno;
close(fd);
@@ -322,6 +513,20 @@ void release(void* p, uint64_t bytes) {
if (!p) return;
#ifdef _WIN32
(void) bytes;
HANDLE section = nullptr;
{
std::lock_guard<std::mutex> lk(g_views_mu);
auto it = g_views.find(p);
if (it != g_views.end()) {
section = it->second;
g_views.erase(it);
}
}
if (section != nullptr) { // a shared-section view: the pages stay while another engine has the section
UnmapViewOfFile(p);
CloseHandle(section);
return;
}
VirtualFree(p, 0, MEM_RELEASE);
#else
munmap(p, bytes);
@@ -410,6 +615,9 @@ PinnedArena::PinnedArena(uint64_t bytes, const std::vector<uint64_t>& bounds,
uint64_t shared_pack_hash) : capacity(bytes) {
if (bytes == 0) return;
base = reserve(bytes, backing, note, shared_file, shared_pack_hash, mapping_base, mapping_bytes);
if (!shared_file.empty() && base != nullptr && mapping_base != nullptr)
shared_state = reinterpret_cast<std::atomic<uint64_t>*>(static_cast<char*>(mapping_base) +
offsetof(SharedArenaHeader, reserved));
if (base != nullptr && mapping_base == nullptr) {
mapping_base = base;
mapping_bytes = bytes;
@@ -529,6 +737,9 @@ PinnedArena::PinnedArena(uint64_t bytes, ByNeed, const std::vector<std::pair<uin
by_need = true;
if (bytes == 0) return;
base = reserve(bytes, backing, note, shared_file, shared_pack_hash, mapping_base, mapping_bytes);
if (!shared_file.empty() && base != nullptr && mapping_base != nullptr)
shared_state = reinterpret_cast<std::atomic<uint64_t>*>(static_cast<char*>(mapping_base) +
offsetof(SharedArenaHeader, reserved));
if (base != nullptr && mapping_base == nullptr) {
mapping_base = base;
mapping_bytes = bytes;
+107
View File
@@ -0,0 +1,107 @@
// Windows selftest for the named-section backing of PinnedArena (the replicas' shared expert arena).
// Parent: two arenas on one section name (same bytes, header refused for another pack / a bigger size, the load state word),
// then a CHILD PROCESS that opens the same section, reads what the parent wrote and writes back; the parent closes its first
// arena while the second still holds the section (the pages must stay), and a third arena joins the live section. After the
// last close, a new arena with the same name starts empty (the section is gone with its last handle).
#ifdef _WIN32
#include "strata/core/pinned.hpp"
#include <windows.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
namespace {
constexpr uint64_t kBytes = 64u << 10;
constexpr uint64_t kPackHash = 0x0123456789abcdefull;
} // namespace
int main(int argc, char** argv) {
const std::vector<uint64_t> no_bounds;
if (argc >= 3 && std::strcmp(argv[1], "child") == 0) {
const std::string name = argv[2];
strata::core::PinnedArena a(kBytes, no_bounds, 0, name, kPackHash);
if (!a.valid()) { std::fprintf(stderr, "child: cannot open: %s\n", a.note.c_str()); return 2; }
if (a.data()[123] != 0x5a) { std::fprintf(stderr, "child: did not see the parent's byte\n"); return 3; }
if (a.shared_state == nullptr || a.shared_state->load() != 4242u) { std::fprintf(stderr, "child: state word is not the parent's\n"); return 4; }
a.data()[456] = 0xa5;
a.shared_state->store(~uint64_t{0}); // (filled)
return 0;
}
int fail = 0;
const std::string name = "win:Local\\strata-pinned-test-" + std::to_string((unsigned long long) GetCurrentProcessId());
{
auto* first = new strata::core::PinnedArena(kBytes, no_bounds, 0, name, kPackHash);
if (!first->valid()) {
std::fprintf(stderr, "first section failed: %s\n", first->note.c_str());
return 1;
}
first->data()[123] = 0x5a;
if (first->shared_state == nullptr) { std::fprintf(stderr, "no state word\n"); fail = 1; }
else if (first->shared_state->load() != 0) { std::fprintf(stderr, "a new section's state word is not 0\n"); fail = 1; }
else first->shared_state->store(4242u);
auto* second = new strata::core::PinnedArena(kBytes, no_bounds, 0, name, kPackHash);
if (!second->valid()) { std::fprintf(stderr, "second section failed: %s\n", second->note.c_str()); fail = 1; }
else {
if (second->data()[123] != 0x5a) { std::fprintf(stderr, "same name did not expose the same bytes\n"); fail = 1; }
if (second->shared_state == nullptr || second->shared_state->load() != 4242u) { std::fprintf(stderr, "state word not shared\n"); fail = 1; }
}
{
strata::core::PinnedArena wrong_pack(kBytes, no_bounds, 0, name, kPackHash + 1);
if (wrong_pack.valid() || wrong_pack.note.find("pack hash") == std::string::npos) {
std::fprintf(stderr, "pack mismatch was not refused: %s\n", wrong_pack.note.c_str());
fail = 1;
}
strata::core::PinnedArena wrong_size(kBytes * 2, no_bounds, 0, name, kPackHash);
if (wrong_size.valid()) { std::fprintf(stderr, "a bigger size was not refused\n"); fail = 1; }
}
// a child process opens the live section
char exe[MAX_PATH];
GetModuleFileNameA(nullptr, exe, MAX_PATH);
std::string cmd = std::string("\"") + exe + "\" child \"" + name + "\"";
STARTUPINFOA si{};
si.cb = sizeof si;
PROCESS_INFORMATION pi{};
std::vector<char> cl(cmd.begin(), cmd.end());
cl.push_back('\0');
if (!CreateProcessA(nullptr, cl.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) {
std::fprintf(stderr, "cannot start the child (error %lu)\n", GetLastError());
fail = 1;
} else {
WaitForSingleObject(pi.hProcess, 120000);
DWORD code = 99;
GetExitCodeProcess(pi.hProcess, &code);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
if (code != 0) { std::fprintf(stderr, "the child failed with %lu\n", code); fail = 1; }
if (first->data()[456] != 0xa5) { std::fprintf(stderr, "the child's byte did not arrive\n"); fail = 1; }
if (first->shared_state->load() != ~uint64_t{0}) { std::fprintf(stderr, "the child's state word did not arrive\n"); fail = 1; }
}
delete first; // the creator is gone; the second still holds the section
if (second != nullptr && second->valid()) {
if (second->data()[123] != 0x5a || second->data()[456] != 0xa5) { std::fprintf(stderr, "bytes lost when the first arena closed\n"); fail = 1; }
strata::core::PinnedArena third(kBytes, no_bounds, 0, name, kPackHash);
if (!third.valid() || third.data()[123] != 0x5a) { std::fprintf(stderr, "a late arena did not join the live section\n"); fail = 1; }
}
delete second;
}
{
strata::core::PinnedArena again(kBytes, no_bounds, 0, name, kPackHash); // (every handle was closed: a new, empty section)
if (!again.valid() || again.data()[123] != 0 || again.shared_state->load() != 0) {
std::fprintf(stderr, "the section survived its last handle\n");
fail = 1;
}
}
std::printf("pinned_shared_win_test: %s\n", fail ? "FAILED" : "OK");
return fail;
}
#else
int main() { return 77; } // (skipped: the Linux build has pinned_shared_test)
#endif
+9 -3
View File
@@ -5949,9 +5949,15 @@ int64_t Prefill::lm_prepare(int64_t n, std::string& why, int64_t cap) {
Impl& m = *impl_;
if (m.g == nullptr) { why = "not initialized"; return 0; }
#if defined(STRATA_USE_HIP) || defined(STRATA_HIP_GFX906)
// only NVIDIA CUDA has run this path; on an AMD build the prompt stays on the chunk-major path
why = "layer-major prefill has only been run on NVIDIA CUDA";
return 0;
// only NVIDIA CUDA has run this path by default; on an AMD build the prompt stays on the chunk-major path unless
// STRATA_LM_HIP=1 (opt-in, PAR-A 2026-10-30: gfx1201 is being qualified)
{
static const bool lm_hip = [] { const char* v = std::getenv("STRATA_LM_HIP"); return v != nullptr && v[0] == '1'; }();
if (!lm_hip) {
why = "layer-major prefill has only been run on NVIDIA CUDA";
return 0;
}
}
#endif
const core::ModelGeometry& g = *m.g;
const core::OnDevice on_device(m.device); // a layer split's later stage is sized on its own GPU
+131
View File
@@ -0,0 +1,131 @@
"""The replica serving profile (opt-in): on two or more GPUs, one engine per card behind the server instead of one layer split.
Measured on 4 x Radeon AI PRO R9700 (Qwen3.8-Flash-Next IQ3_S, 1k-token prompts, 128 tokens out, total tokens per second;
docs/BATCHING.md, "Replicas as a serving profile"):
* 2 cards: 2 engines of one card each reach 1.8 to 1.9 times what a layer split of the two cards does with `parallel` 8 or
16 (116.6 against 63.8 tok/s at 8 requests, 126.9 against 65.7 at 16);
* 4 cards: 4 engines reach 1.8 to 1.9 times the shipped four-card profile (190 against 107 at 8, 220 against 116 at 16).
Nothing is lost for one user: a request alone runs on one card at the same speed (13 ms a token on one card, 13 ms on four).
The price is host RAM: every engine keeps its own copy of the experts. The profile therefore turns on the shared expert
arena (serve/replicas.py: one memory object holds the experts for all engines, measured 61 GB for 2 engines and 66 GB for 4
instead of 109 and 211 GB) and chooses the number of engines the RAM allows. Without the shared arena (Windows) it needs the
private copies to fit.
Recommend, never force: this profile is opt-in (`setup.py --replica-profile`, STRATA_REPLICA_PROFILE=1, or
"replica_profile": true in the run config; "replica_profile": false or STRATA_REPLICA_PROFILE=0 turn it off), and what the
person chose (the config's `replicas`, `--parallel`, `--layer-split`, `--peer-device`, an environment variable) always wins.
It only touches the measured model class; every other computer gets exactly what it got before.
Pure functions, no I/O (the callers pass the cards, the RAM and the run config).
"""
from __future__ import annotations
import os
import sys
# the experts' size in RAM per quant (setup.py MODELS "arena_gb"): the shared arena holds this once
ARENA_GB = {"iq3_s": 50.3, "iq3_xxs": 42.9, "q2_0": 34.0}
PER_REPLICA_GB = 8.0 # measured: about 7 GB of private RAM per engine beside the arena (2 engines: 61 GB in all, 47 of them shared)
HEADROOM_GB = 12.0 # what the OS, the server and the page cache keep
MIN_CARD_GB = 24.0 # measured on 32 GB cards; a smaller card is not claimed (an A4000 16 GB is measured separately)
MIN_REPLICAS = 2
SWITCHES = {"STRATA_QSA_SLOTS": "1", "STRATA_BATCH_SELF_COMMIT": "1", "STRATA_SLOT_COPY_DIRECT": "1",
"STRATA_CKPT_POOL": "1", "STRATA_CKPT_POOL_READY": "4", "STRATA_SLOT_CKPT_MOVE": "1",
"STRATA_SLOT_COPY_BATCHSYNC": "1"}
OFF_VALUES = ("0", "off", "no", "false")
ON_VALUES = ("1", "on", "yes", "true")
def requested(cfg: dict | None = None, environ=None) -> bool:
"""Opt-in, and an explicit no wins: config "replica_profile" true/false, else STRATA_REPLICA_PROFILE."""
environ = os.environ if environ is None else environ
if cfg and cfg.get("replica_profile") is not None:
return cfg.get("replica_profile") is True
return str(environ.get("STRATA_REPLICA_PROFILE", "")).strip().lower() in ON_VALUES
def quant_of(cfg: dict) -> str | None:
"""The measured model: Qwen3.8-Flash-Next at a quant whose experts size is known."""
text = " ".join([str(cfg.get("model_name") or "")] + [str(a) for a in cfg.get("args") or []]).lower().replace("-", "_").replace(".", "_")
if "qwen3_8" not in text:
return None
for q in ARENA_GB:
if q in text:
return q
return None
def shared_arena_possible(platform: str | None = None) -> bool:
return (sys.platform if platform is None else platform).startswith(("linux", "win"))
def replicas_for(n_cards: int, arena_gb: float, ram_gb: float, shared: bool) -> int:
"""How many one-card engines the RAM allows (at most one per card)."""
if shared:
room = ram_gb - HEADROOM_GB - arena_gb
k = int(room // PER_REPLICA_GB) if room > 0 else 0
else:
k = int((ram_gb - HEADROOM_GB) // (arena_gb + PER_REPLICA_GB))
return max(0, min(n_cards, k))
def applies(cards: list[dict], cfg: dict, ram_gb: float, *, platform: str | None = None, environ=None) -> tuple[bool, str]:
"""(True, "") when the profile would be applied; else (False, why not) - the reason is for the log, '' when nothing was asked."""
if not requested(cfg, environ):
return False, ""
args = [str(a) for a in cfg.get("args") or []]
if cfg.get("replicas") or cfg.get("arch") == "deepseek41" or "--peer-device" in args:
return False, "replica profile: the config already names replicas, a peer device or another model family"
if str(cfg.get("layer_split") or "auto") != "auto" or "--layer-split" in args:
return False, "replica profile: you chose a layer split; kept"
if len(cards) < MIN_REPLICAS:
return False, "replica profile: needs two or more GPUs"
q = quant_of(cfg)
if q is None:
return False, "replica profile: measured only for Qwen3.8-Flash-Next IQ3_S, IQ3_XXS and Q2_0"
if any(float(c.get("vram_gb") or 0.0) < MIN_CARD_GB for c in cards):
return False, f"replica profile: a card has under {MIN_CARD_GB:.0f} GB of VRAM (measured on 32 GB cards)"
shared = shared_arena_possible(platform)
k = replicas_for(len(cards), ARENA_GB[q], ram_gb, shared)
if k < MIN_REPLICAS:
need = HEADROOM_GB + ARENA_GB[q] + MIN_REPLICAS * PER_REPLICA_GB if shared else HEADROOM_GB + MIN_REPLICAS * (ARENA_GB[q] + PER_REPLICA_GB)
return False, (f"replica profile: {ram_gb:.0f} GB of RAM; two engines need about {need:.0f} GB"
f"{'' if shared else ' (this OS cannot share the experts between engines)'}; the layer split stays")
return True, ""
def apply(cards: list[dict], cfg: dict, ram_gb: float, *, parallel_given: bool = False, platform: str | None = None,
environ=None) -> list[str]:
"""Fill the profile into `cfg` (in place) where the person has not chosen; returns the plain lines to show."""
ok, why = applies(cards, cfg, ram_gb, platform=platform, environ=environ)
if not ok:
return [why] if why else []
environ = os.environ if environ is None else environ
q = quant_of(cfg)
shared = shared_arena_possible(platform)
k = replicas_for(len(cards), ARENA_GB[q], ram_gb, shared)
gpus = cfg.get("gpu") if isinstance(cfg.get("gpu"), list) else [c.get("index", i) for i, c in enumerate(cards)]
chosen = list(gpus)[:k]
cfg["gpu"] = chosen
cfg["replicas"] = [{"gpus": [g]} for g in chosen]
if shared:
cfg.setdefault("replica_shared_arena", True)
env = dict(cfg.get("env") or {})
for key, val in SWITCHES.items():
if key not in environ: # an environment variable of the person's own wins
env.setdefault(key, val)
cfg["env"] = env
notes = [f"{k} engines of one card each instead of one layer split of {len(cards)}: requests spread over them "
f"(about 1.8 times the throughput at 8 to 16 requests at once; one request alone is as fast as before)"]
if k < len(cards):
notes.append(f"{len(cards) - k} card(s) left out: the RAM ({ram_gb:.0f} GB) holds {k} engines")
notes.append("the experts are in RAM once for all engines (shared expert arena)" if shared else
"each engine keeps its own copy of the experts in RAM (this OS cannot share them)")
if not parallel_given and "parallel" not in cfg and not {"--batch", "--slots"} & set(str(a) for a in cfg.get("args") or []):
cfg["parallel"] = 8 if k == 2 else 4
notes.append(f"{cfg['parallel']} requests at once on each engine (--parallel N changes it)")
return notes
+130
View File
@@ -0,0 +1,130 @@
"""strata_control/replica_profile.py: the opt-in replica serving profile (one engine per card, shared expert arena)."""
from __future__ import annotations
import unittest
from strata_control import replica_profile as P
R9700 = {"name": "AMD Radeon AI PRO R9700", "vram_gb": 31.9, "vendor": "amd"}
def cards(n, **kw):
return [{**R9700, **kw, "index": i} for i in range(n)]
def cfg(**kw):
c = {"model_name": "qwen3.8-flash-next-iq3_s", "gpu": [0, 1, 2, 3], "layer_split": "auto",
"args": ["--pack", "/p/iq3_s", "--spec", "4"], "replica_profile": True}
c.update(kw)
return c
class Request(unittest.TestCase):
def test_opt_in_only(self):
self.assertFalse(P.requested({}, {}))
self.assertTrue(P.requested({"replica_profile": True}, {}))
self.assertTrue(P.requested({}, {"STRATA_REPLICA_PROFILE": "1"}))
def test_an_explicit_no_wins(self):
self.assertFalse(P.requested({"replica_profile": False}, {"STRATA_REPLICA_PROFILE": "1"}))
def test_without_the_request_nothing_changes(self):
c = cfg(replica_profile=None)
c.pop("replica_profile")
before = dict(c)
self.assertEqual(P.apply(cards(4), c, 256.0, platform="linux", environ={}), [])
self.assertEqual(c, before)
class Count(unittest.TestCase):
def test_ram_sets_the_number_of_engines(self):
self.assertEqual(P.replicas_for(4, 50.3, 251.0, True), 4)
self.assertEqual(P.replicas_for(4, 50.3, 128.0, True), 4)
self.assertEqual(P.replicas_for(4, 50.3, 96.0, True), 4)
self.assertEqual(P.replicas_for(4, 50.3, 80.0, True), 2) # 80 - 12 - 50.3 = 17.7 -> 2
self.assertEqual(P.replicas_for(4, 50.3, 64.0, True), 0) # one engine's room only
self.assertEqual(P.replicas_for(2, 50.3, 251.0, False), 2) # private copies: 2 x 58.3 + 12
self.assertEqual(P.replicas_for(4, 50.3, 200.0, False), 3)
self.assertEqual(P.replicas_for(2, 50.3, 100.0, False), 1)
class Apply(unittest.TestCase):
def test_four_cards_with_plenty_of_ram(self):
c = cfg()
notes = P.apply(cards(4), c, 251.0, platform="linux", environ={})
self.assertTrue(notes)
self.assertEqual(c["replicas"], [{"gpus": [0]}, {"gpus": [1]}, {"gpus": [2]}, {"gpus": [3]}])
self.assertIs(c["replica_shared_arena"], True)
self.assertEqual(c["parallel"], 4)
self.assertEqual(c["env"]["STRATA_CKPT_POOL"], "1")
self.assertEqual(c["env"]["STRATA_QSA_SLOTS"], "1")
def test_two_cards_get_eight_slots_each(self):
c = cfg(gpu=[0, 1])
P.apply(cards(2), c, 128.0, platform="linux", environ={})
self.assertEqual(c["parallel"], 8)
self.assertEqual(len(c["replicas"]), 2)
def test_less_ram_drops_cards_and_says_so(self):
c = cfg()
notes = P.apply(cards(4), c, 80.0, platform="linux", environ={})
self.assertEqual(c["replicas"], [{"gpus": [0]}, {"gpus": [1]}])
self.assertEqual(c["gpu"], [0, 1])
self.assertTrue(any("left out" in n for n in notes))
def test_too_little_ram_keeps_the_layer_split(self):
c = cfg()
notes = P.apply(cards(4), c, 64.0, platform="linux", environ={})
self.assertNotIn("replicas", c)
self.assertEqual(c["layer_split"], "auto")
self.assertTrue(notes and "layer split stays" in notes[0])
def test_windows_shares_through_a_section(self):
c = cfg(gpu=[0, 1])
P.apply(cards(2), c, 80.0, platform="win32", environ={})
self.assertEqual(len(c["replicas"]), 2)
self.assertIs(c["replica_shared_arena"], True)
def test_an_os_that_cannot_share_needs_the_private_copies_to_fit(self):
c = cfg(gpu=[0, 1])
self.assertEqual(P.apply(cards(2), c, 100.0, platform="darwin", environ={})[0].split(":")[0], "replica profile")
self.assertNotIn("replicas", c)
c = cfg(gpu=[0, 1])
P.apply(cards(2), c, 160.0, platform="darwin", environ={})
self.assertEqual(len(c["replicas"]), 2)
self.assertNotIn("replica_shared_arena", c)
def test_what_the_person_chose_wins(self):
for kw in ({"replicas": 2}, {"layer_split": "13,26,39"}, {"args": ["--pack", "/p/iq3_s", "--peer-device", "1"]},
{"args": ["--pack", "/p/iq3_s", "--layer-split", "20,40"]}, {"arch": "deepseek41"}):
c = cfg(**kw)
before = dict(c)
P.apply(cards(4), c, 251.0, platform="linux", environ={})
self.assertEqual(c.get("replicas"), before.get("replicas"), kw)
self.assertEqual(c.get("layer_split"), before.get("layer_split"), kw)
def test_parallel_and_environment_variables_of_the_person_win(self):
c = cfg(parallel=6)
P.apply(cards(4), c, 251.0, platform="linux", environ={"STRATA_CKPT_POOL": "0"})
self.assertEqual(c["parallel"], 6)
self.assertNotIn("STRATA_CKPT_POOL", c["env"])
c = cfg()
P.apply(cards(4), c, 251.0, parallel_given=True, platform="linux", environ={})
self.assertNotIn("parallel", c)
def test_only_the_measured_hardware_and_model(self):
for kw in ({"model_name": "qwen3.6-35b-a3b-q4"}, {"model_name": "qwen3.8-flash-next-iq2_xs", "args": ["--pack", "/p/x"]}):
c = cfg(**kw)
P.apply(cards(4), c, 251.0, platform="linux", environ={})
self.assertNotIn("replicas", c, kw)
c = cfg(gpu=[0, 1])
P.apply(cards(2, vram_gb=16.0), c, 251.0, platform="linux", environ={})
self.assertNotIn("replicas", c)
c = cfg(gpu=[0])
P.apply(cards(1), c, 251.0, platform="linux", environ={})
self.assertNotIn("replicas", c)
if __name__ == "__main__":
unittest.main()