mirror of
https://github.com/Niko1221/Strata.git
synced 2026-11-07 22:59:00 -10:00
Strix Halo: an oversized prompt chunk is refused at the start instead of killing the engine after the prefill
An integrated GPU cannot report how much room is left (the pool reads 0 MiB free on a start that works). With a chunk above the 16384 default that the person or the environment names (STRATA_PREFILL_AUTO_MAX=32768, --prefill auto:32768) and a UD-Q4_K_XL cache that fills the pool, the driver could not create a stream (PAL failed to submit CMD) and the first launch after the prompt buffers died with HIP 719. Once the prompt path's own buffers are in place the engine now tries one stream, fill and wait; if that fails it prints one line naming the chunk, the cache and the 16384 alternative and ends with exit code 2, before any prompt is read (HIP builds, integrated GPU, own buffers, chunk above 16384 only; the 16384 default, CUDA and Metal are untouched; STRATA_PF_OWN_PROBE_OFF=1 skips it). The failed context cannot be reused, so the engine cannot step down in the same process. The #1630 warning about a named chunk of 8192 or more no longer prints on gfx1151 (it said a bare --prefill auto stops at 6144, stale since 0.2.8); it is printed once the device is known. STRATA_TRACE marks around the prompt path and the buffer size of the chunk. setup: the bench tip for --prefill auto:32768 is not said on an APU (apu_hides_chunk_tip). The tuner's prefill_32k arm needs a discrete GPU. The app's saferStart does not retry a start that ended with the chunk refusal (the same chunk would fail again, and a memory setting is not the cause). Tests for all three.
This commit is contained in:
1 parent
938b76eb3e
commit
8b98b6f1ca
7 files changed
+109
-5
No files matched your search
@@ -125,6 +125,8 @@ export const engineVersionOfExe=exe=>{const m=/engine[\\/](\d+(?:\.\d+)+)-/.exec
|
||||
technical line for Details. The caller tries once and never again, and does not save this config. */
|
||||
export function saferStart(config,hardware,model,failure){
|
||||
const text=String(failure??'');
|
||||
// The engine refused an oversized prompt chunk at the start (an APU whose pool the expert cache fills; the person or the environment named the chunk): the same chunk would fail again, and a memory setting is not the cause.
|
||||
if(/prompt chunk does not fit/i.test(text))return null;
|
||||
// A start that died for a reason that is not memory (a missing or bad DLL, a crash, a blocked file) is not helped by a memory setting, and must not be called a memory failure.
|
||||
if(/0xC0000135|0xC000007B|0xC0000142|0xC000001D|0xC0000005|0xC0000409|0xC0000022|error while loading shared libraries|cannot open shared object file|\bENOENT\b|exit code 127|exit code 139|illegal instruction/i.test(text))return null;
|
||||
const ranOut=/out of memory|memory guard|\boom\b|\bkilled\b|exit code 137|cannot allocate|not enough memory|bad_alloc|insufficient memory/i.test(text),first=ranOut?'The first start ran out of memory':'The first start did not finish';
|
||||
|
||||
@@ -230,3 +230,11 @@ test('0.2.3 tuning: Work keeps a fifth of the card but no more than 1536 MiB on
|
||||
assert.equal(resourceSettings({ gpu: 0, args: [], vision: { gpu: true } }, card(12), 'work').vram_reserve_mib, 2048);
|
||||
assert.equal(resourceSettings({ gpu: 0, args: ['--vram-reserve-mib', '3000'], user_args: ['--vram-reserve-mib', '3000'] }, card(12), 'work').vram_reserve_mib, 3000, 'the person\'s own number is kept');
|
||||
});
|
||||
|
||||
test('an engine that refused the prompt chunk at the start is not retried with a memory setting: the same chunk would fail again', () => {
|
||||
const line = 'strata generate: a 32768-token prompt chunk does not fit beside the 52.1 GiB expert cache on this GPU (a stream could not be created): use --prefill auto:16384, or a smaller --expert-cache';
|
||||
const withBudget = { ...config, args: [...config.args, '--resident-budget-gib', '40'] };
|
||||
assert.equal(saferStart(withBudget, pc64, iq3s, `server_exited (exit code 2): ${line}`), null);
|
||||
assert.equal(saferStart(config, pc64, iq3s, `Engine stopped (exit code 2): ${line}`), null);
|
||||
assert.ok(saferStart(withBudget, pc64, iq3s, 'server_exited: memory guard: 1.9 GiB free'), 'a real memory stop still gets its safer try');
|
||||
});
|
||||
@@ -3981,6 +3981,12 @@ def is_uma_apu(g) -> bool:
|
||||
or (GFX1103_OPT_IN and gfx_arch_is(arch, "gfx1103")))
|
||||
|
||||
|
||||
def apu_hides_chunk_tip(g, line: str) -> bool:
|
||||
"""The bench tip for --prefill auto:32768 is not said on an APU: its prompt buffers share the pool with the expert cache, a 32768 chunk can take the last of it
|
||||
(Strix Halo with UD-Q4_K_XL: the engine ends at the start, round0212), and the tip was measured on discrete cards."""
|
||||
return is_uma_apu(g) and line.startswith("tip") and "auto:32768" in line
|
||||
|
||||
|
||||
def amd_apply_uma(g: dict, gtt_gb: float = 0.0, ram: float | None = None) -> dict:
|
||||
"""Strix Halo is an APU with unified memory. Its "dedicated VRAM" is a BIOS carve-out (512 MB to 96 GB; 2 GB on the
|
||||
maintainers' box) and the GPU also reaches shared system memory (Linux: the GTT pool, `mem_info_gtt_total`, half the
|
||||
@@ -10969,6 +10975,8 @@ def main() -> int:
|
||||
for line in two_socket_note(cpu_sockets()): # bench tips: text only, the config is not touched
|
||||
say(" " + line)
|
||||
for line in bench_tips(cfg["args"], cfg.get("env"), ram, MODELS[model]["ram_gb"], gpu.get("vram_gb", 0.0), vision, WIN):
|
||||
if apu_hides_chunk_tip(gpu, line):
|
||||
continue
|
||||
say(" " + line)
|
||||
write_setup_config(cfg_path, cfg, adopted if adopted is not None and adopted.name == cfg_path.name else None, explicit_args(a),
|
||||
after_carry=(lambda c: uma_cache_guard(c, model, gpu)) if gpu.get("uma") else None)
|
||||
|
||||
@@ -820,6 +820,10 @@ struct Options {
|
||||
/// must not fall back to 2048 ("reserved buffers, keep them small") - the gfx1151 rule above has already chosen a
|
||||
/// chunk that fits this box's carve-out.
|
||||
bool prefill_own_keep_chunk = false;
|
||||
/// #1630's warning for a chunk the user named (8192 or more on a Windows HIP build), held until the device is known: it was printed at argument
|
||||
/// parsing, so it also appeared on the Strix Halo (gfx1151), where 8192 and 16384 are the measured default and a bare auto no longer stops at 6144.
|
||||
std::string prefill_cap_warn;
|
||||
bool prefill_cap_warn_skip = false; ///< set by the gfx1151 rule below: that card is not the 7900 XTX of #1630
|
||||
bool prefill_gfx12_16k = false; ///< the gfx12 default raised the auto chunk ceiling to 16384 (a layer split takes 8192 back, 0.1.44)
|
||||
/// --prefill-pipe (opt-in): a layer split's prompt chunks sized for its pipeline (Prefill::pipeline_chunk): 0 off,
|
||||
/// 1 today's chunk count evened out, a number > 1 the rig's b/a in tokens (setup's --calibrate measures it).
|
||||
@@ -1891,6 +1895,49 @@ void stall_report(std::FILE* f, uint64_t layers_during) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/// One more launch with the prompt path's buffers in place: a 64 MiB allocation, a stream, a fill and a wait. On an integrated GPU whose pool is full the driver cannot
|
||||
/// build the command buffer ("PAL failed to submit CMD") and every later launch fails with "unspecified launch failure"; this finds it before the prompt is read.
|
||||
#if defined(STRATA_USE_HIP)
|
||||
static bool uma_launch_probe(std::string& why) {
|
||||
(void) cudaGetLastError();
|
||||
void* p = nullptr;
|
||||
if (cudaMalloc(&p, 64ull << 20) != cudaSuccess) { why = "a 64 MiB allocation failed"; (void) cudaGetLastError(); return false; }
|
||||
cudaStream_t st = nullptr;
|
||||
bool ok = cudaStreamCreateWithFlags(&st, cudaStreamNonBlocking) == cudaSuccess;
|
||||
if (!ok) why = "a stream could not be created";
|
||||
if (ok) ok = cudaMemsetAsync(p, 0, 64ull << 20, st) == cudaSuccess && cudaStreamSynchronize(st) == cudaSuccess && cudaGetLastError() == cudaSuccess;
|
||||
if (!ok && why.empty()) why = "a launch failed";
|
||||
if (st != nullptr) cudaStreamDestroy(st);
|
||||
cudaFree(p);
|
||||
if (!ok) (void) cudaGetLastError();
|
||||
return ok;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Integrated GPU (Strix Halo), the prompt path's own buffers, a chunk above the 16384 default that the person or the environment named: the unified pool reads 0 MiB free
|
||||
/// on a start that works, so how much room is left cannot be read, and 32768 on a UD-Q4_K_XL cache that fills the pool takes the last of it (the driver then cannot create a
|
||||
/// stream, and the context is unusable from then on: HIP 719 at the first launch, after the prompt was read). So once the buffers are in place one stream, fill and wait are
|
||||
/// tried; if they fail the engine says so in one line and ends, before any prompt is read, instead of dying after the prefill. It cannot step down in the same process (the
|
||||
/// failed context aborts on the first call that touches it). Never for the 16384 default, a chunk that borrows cache slots, or a discrete GPU. STRATA_PF_OWN_PROBE_OFF=1 skips it.
|
||||
static void uma_refuse_chunk_if_full(int64_t chunk, bool borrows, double cache_gib) {
|
||||
#if !defined(STRATA_USE_HIP)
|
||||
(void) chunk; (void) borrows; (void) cache_gib; // observed on the Strix Halo (HIP) only: CUDA and Metal builds are untouched
|
||||
return;
|
||||
#else
|
||||
if (borrows || chunk <= 16384 || spill_discrete_gpu() || std::getenv("STRATA_PF_OWN_PROBE_OFF") != nullptr) return;
|
||||
std::string why;
|
||||
if (uma_launch_probe(why)) {
|
||||
if (std::getenv("STRATA_TRACE") != nullptr) std::fprintf(stderr, "strata trace: the launch probe passed with a %lld-token chunk\n", (long long) chunk);
|
||||
return;
|
||||
}
|
||||
const int64_t next = strata::core::gfx12_chunk_step_down(chunk);
|
||||
std::fprintf(stderr, "strata generate: a %lld-token prompt chunk does not fit beside the %.1f GiB expert cache on this GPU (%s): use --prefill auto:%lld, or a smaller --expert-cache\n",
|
||||
(long long) chunk, cache_gib, why.c_str(), (long long) next);
|
||||
std::fflush(stderr);
|
||||
std::_Exit(2);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// STRATA_TRACE=1: the VRAM left at a step of the startup (finds what fills the card after the cache is sized)
|
||||
void mem_mark(const char* where) {
|
||||
static const char* const env = std::getenv("STRATA_TRACE");
|
||||
@@ -3495,10 +3542,7 @@ int main(int argc, char** argv) {
|
||||
o.prefill_auto_cap = strata::platform::host_limits::auto_chunk_cap(win_hip, bare_auto && o.prefill_auto);
|
||||
if (o.prefill_auto_cap > 0) o.prefill_chunk = std::min(o.prefill_chunk, o.prefill_auto_cap);
|
||||
if (strata::platform::host_limits::warn_user_chunk(win_hip, bare_auto, o.prefill_chunk))
|
||||
std::fprintf(stderr, "strata: WARNING --prefill %s: prompt chunks of 8192 tokens or more crashed the engine "
|
||||
"on Windows HIP (RX 7900 XTX: the commit charge jumped ~70 GB, exit code 0xC0000409, "
|
||||
"#1630); 7500 and below were stable. Kept as asked; a bare --prefill auto stops at %lld\n",
|
||||
v.c_str(), (long long) strata::platform::host_limits::kWinHipAutoChunkCap);
|
||||
o.prefill_cap_warn = v; // printed once the device is known (gfx1151 does not print it)
|
||||
}
|
||||
}
|
||||
else if (a == "--prefill-pipe") o.prefill_pipe = std::max(0.0, std::atof(next("--prefill-pipe")));
|
||||
@@ -3756,6 +3800,11 @@ int main(int argc, char** argv) {
|
||||
// gfx1151 on Windows (#1630 capped a bare `--prefill auto` at 6144 for a 7900 XTX's commit charge): the Strix Halo has its
|
||||
// 8192 chunk back. IQ3_S, 8K prompt: 691 -> 787 tok/s (one chunk streams 1,785 experts instead of 2,782), 64K 713 -> 748;
|
||||
// 4K is one chunk already. STRATA_GFX1151_CHUNK=0 (or STRATA_GFX1151_DEFAULTS=0) keeps 6144.
|
||||
if (win_host && all_igpu && std::strncmp(ad.gcnArchName, "gfx1151", 7) == 0) { // #1630 was a discrete RX 7900 XTX: no warning about a named chunk here
|
||||
const char* c1w = std::getenv("STRATA_GFX1151_CHUNK");
|
||||
const char* d1w = std::getenv("STRATA_GFX1151_DEFAULTS");
|
||||
if (!(c1w != nullptr && c1w[0] == '0') && !(d1w != nullptr && d1w[0] == '0')) o.prefill_cap_warn_skip = true;
|
||||
}
|
||||
if (win_host && all_igpu && o.prefill_auto_bare && o.prefill_auto_cap > 0 && std::strncmp(ad.gcnArchName, "gfx1151", 7) == 0) {
|
||||
const char* c1 = std::getenv("STRATA_GFX1151_CHUNK");
|
||||
const char* d1 = std::getenv("STRATA_GFX1151_DEFAULTS");
|
||||
@@ -3787,6 +3836,11 @@ int main(int argc, char** argv) {
|
||||
} else (void) cudaGetLastError();
|
||||
}
|
||||
#endif
|
||||
if (!o.prefill_cap_warn.empty() && !o.prefill_cap_warn_skip)
|
||||
std::fprintf(stderr, "strata: WARNING --prefill %s: prompt chunks of 8192 tokens or more crashed the engine "
|
||||
"on Windows HIP (RX 7900 XTX: the commit charge jumped ~70 GB, exit code 0xC0000409, "
|
||||
"#1630); 7500 and below were stable. Kept as asked; a bare --prefill auto stops at %lld\n",
|
||||
o.prefill_cap_warn.c_str(), (long long) strata::platform::host_limits::kWinHipAutoChunkCap);
|
||||
#if !defined(STRATA_USE_HIP) && !defined(STRATA_GPU_METAL)
|
||||
{ // CUDA sm_86 / sm_60: the switches that are exact AND faster there are on by default (strata/core/nv_defaults.hpp), when every
|
||||
// visible card has that one compute capability; before any engine code reads its switches, the user's own settings are kept
|
||||
@@ -11046,6 +11100,7 @@ int main(int argc, char** argv) {
|
||||
"context, or read prompts in smaller chunks (--prefill 512)\n");
|
||||
return 1;
|
||||
}
|
||||
uma_refuse_chunk_if_full(o.prefill_chunk, borrow != nullptr, xcache.gib());
|
||||
// The peer's streaming and a split's helpers live INSIDE the layout (`set_peer` / `set_stage_helper` build
|
||||
// them in the path's own Impl), so a path that is laid out again - the prompt room, below - has to be armed
|
||||
// again. Both calls start by dropping what they built before, and both leave `err` clear on a decline.
|
||||
@@ -19641,6 +19696,19 @@ int main(int argc, char** argv) {
|
||||
std::fprintf(stderr, "strata generate: %s\n", err.c_str());
|
||||
return 1;
|
||||
}
|
||||
uma_refuse_chunk_if_full(o.prefill_chunk, borrow != nullptr, xcache.gib());
|
||||
mem_mark("the prompt path's buffers");
|
||||
if (std::getenv("STRATA_TRACE") != nullptr) { // what the buffers of this chunk take, and (Windows) the commit left
|
||||
std::fprintf(stderr, "strata trace: the prompt path's own buffers for a %lld-token chunk: %lld MiB (bytes_needed_owned)", (long long) o.prefill_chunk,
|
||||
(long long) (strata::prefill::Prefill::bytes_needed_owned(g, ss, o.prefill_chunk, srcp != nullptr) >> 20));
|
||||
#if defined(_WIN32)
|
||||
MEMORYSTATUSEX ms_t{};
|
||||
ms_t.dwLength = sizeof ms_t;
|
||||
if (GlobalMemoryStatusEx(&ms_t))
|
||||
std::fprintf(stderr, "; commit left %.1f GiB, RAM available %.1f GiB", (double) ms_t.ullAvailPageFile / 1073741824.0, (double) ms_t.ullAvailPhys / 1073741824.0);
|
||||
#endif
|
||||
std::fprintf(stderr, "\n");
|
||||
}
|
||||
if (!o.mtp.empty()) {
|
||||
if (!mtp.bind(wt, &native_head, nullptr, err)) {
|
||||
std::fprintf(stderr, "strata generate: %s\n", err.c_str());
|
||||
@@ -19703,6 +19771,7 @@ int main(int argc, char** argv) {
|
||||
#endif
|
||||
prefill_batched_ms = std::chrono::duration<double, std::milli>(Clock::now() - tp0).count();
|
||||
prefill_ms += prefill_batched_ms;
|
||||
mem_mark("the prompt was read");
|
||||
pos_start = n_batched;
|
||||
tok = o.tokens[(size_t) pos_start];
|
||||
// the PLE window of the token path: the two tokens before `pos_start`
|
||||
|
||||
@@ -84,7 +84,7 @@ ARMS: tuple[Arm, ...] = (
|
||||
classes=("A", "B", "C"), backends=("cuda", "hip"), needs=("ctx_ge_128k",)),
|
||||
# --- prompt speed: the chunk size is exact (10/10) and costs no decode speed on a card with VRAM to spare ------------------
|
||||
Arm("prefill_32k", "Larger prompt chunks (faster very long prompts)", "exact", {"--prefill": "auto:32768"},
|
||||
classes=("B",), goals=("coding", "chat"), ram_delta_mib=4096, needs=("vram_ge_24g", "long_prompts"), metric="prompt"),
|
||||
classes=("B",), goals=("coding", "chat"), ram_delta_mib=4096, needs=("vram_ge_24g", "long_prompts", "discrete_gpu"), metric="prompt"),
|
||||
# --- class A: RAM-bound resident mode (RTX 3060 / 46 GB): the one measured gain is adapt-async, behind its own switch ------
|
||||
Arm("adapt_async", "Learn which experts to keep while you chat, in the background", "nonrepeatable",
|
||||
{"--adapt-async": 1}, backends=("cuda",), classes=("A",), needs=("resident_ram", "one_gpu")),
|
||||
@@ -220,6 +220,7 @@ def condition_holds(name: str, ctx: Context) -> bool:
|
||||
"hybrid_cpu": ctx.hybrid_cpu,
|
||||
"hybrid_experts": bool(f.get("hybrid_experts")),
|
||||
"vram_ge_24g": ctx.vram_mib >= 24000,
|
||||
"discrete_gpu": not (ctx.arch and ctx.arch.split(":")[0] in ("gfx1151", "gfx1150", "gfx1103")), # an APU's pool is shared with the expert cache
|
||||
"vram_le_8g": 0 < ctx.vram_mib <= 8400,
|
||||
"gfx12": bool(ctx.arch and ctx.arch.startswith("gfx12")),
|
||||
"windows_amd_kv_stream": ctx.backend == "hip" and sys.platform == "win32" and "--kv-resident" in ctx.base_args,
|
||||
|
||||
@@ -95,6 +95,16 @@ class RulesTest(unittest.TestCase):
|
||||
short.base_args = ["--max-context", "32768"]
|
||||
self.assertNotIn("kv_resident_64k", [p.arm.id for p in tune_knobs.plan_arms(short, mode="quick")[0]])
|
||||
|
||||
def test_the_larger_chunk_is_not_tried_on_an_apu(self):
|
||||
# round0212: on a Strix Halo the prompt buffers share the pool with the expert cache; a 32768 chunk on UD-Q4_K_XL ends the engine at the start
|
||||
ctx = make_ctx()
|
||||
ctx.vram_mib = 90000 # an APU's pool reads large
|
||||
ctx.facts = {"workers": 31}
|
||||
ctx.arch = "gfx1151"
|
||||
self.assertNotIn("prefill_32k", [p.arm.id for p in tune_knobs.plan_arms(ctx, mode="quick")[0]])
|
||||
ctx.arch = "gfx1201"
|
||||
self.assertIn("prefill_32k", [p.arm.id for p in tune_knobs.plan_arms(ctx, mode="quick")[0]])
|
||||
|
||||
def test_a_prompt_knob_is_judged_on_prompt_speed_and_may_not_cost_decode(self):
|
||||
ctx = make_ctx()
|
||||
ctx.vram_mib = 32624
|
||||
|
||||
@@ -32,6 +32,12 @@ class BenchTips(unittest.TestCase):
|
||||
self.assertEqual(tips(a, win=False), [])
|
||||
self.assertEqual(tips(BASE, win=True), []) # auto: nothing to warn about
|
||||
|
||||
def test_the_chunk_tip_is_not_said_on_an_apu(self):
|
||||
tip = next(t for t in tips(ram=128) if "auto:32768" in t)
|
||||
self.assertTrue(setup.apu_hides_chunk_tip({"arch": "gfx1151", "uma": True}, tip)) # Strix Halo
|
||||
self.assertFalse(setup.apu_hides_chunk_tip({"arch": "gfx1201"}, tip)) # a discrete card keeps it
|
||||
self.assertFalse(setup.apu_hides_chunk_tip({"arch": "gfx1151", "uma": True}, "warning: --prefill auto:32768 on 32 GB of RAM"))
|
||||
|
||||
def test_prefill_auto_32768_follows_the_ram(self):
|
||||
self.assertTrue(any("auto:32768" in t and t.startswith("tip") for t in tips(ram=128)))
|
||||
self.assertEqual([t for t in tips(ram=64) if "auto:32768" in t], [])
|
||||
|
||||
Reference in new issue
Block a user