Compare commits

..
8 Commits
Author SHA1 Message Date
Tarek Dakhran f174553e16 server : fix swa checkpoint invalidation logic (#30666)
* server : fix swa checkpoint invalidation logic

Assisted-by: Claude

* add testcase that fails with > n_past, but works with > n_prefix
2026-10-19 15:58:14 +02:00
Alessandro de Oliveira Faria (A.K.A.CABELO) 7951f601e8 vendor : update cpp-httplib to 0.62.0 (#30785) 2026-10-19 20:23:23 +08:00
Evan Huus 33d6135cbe moe-cache : replace MoE LRU cache with a 50/50 SLRU cache (#30637) 2026-10-19 13:28:50 +02:00
Georgi Gerganov 098ce526b5 metal : fix moe_reduce fusion race when dst aliases its inputs (#30795)
The fused moe_reduce kernel computes dst = sum(experts[e] * weights[e]) in a
single dispatch, reading experts and weights while writing dst. The graph
allocator computes tensor liveness on the unfused graph, where the inputs are
fully consumed by earlier nodes of the fused group, so it can legally recycle
the input memory as dst. In the fused kernel this becomes a
threadgroup-vs-threadgroup race that produces rare non-deterministic wrong
results (observed with test-llama-archs -a kolibri on the meta backend).

Reject the fusion when dst overlaps either input, same guard as the
mul_mat_add fusion. The unfused MUL + ADD path is always correct.

Assisted-by: pi:llama.cpp/ggml-org/GLM-5.3-Flash-GGUF (2x DGX Spark (RDMA, TP=2))
2026-10-19 11:55:02 +03:00
Raul Torres f3fb2fcdbc cann : refactor #ifdef blocks to avoid unreachable code after return (#25273) 2026-10-19 11:17:54 +03:00
Aaron Teo fda00f18cd tests: add big-endian conversion tests (#30705)
* ci: add quantize to big-endian tests

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* ci: remove incoherent data types from testing

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* tests: add verified hashes

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* tests: rename

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* tests: add result table

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* tests: swap hash and result column, add colours

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* tests: switch to perplexity test

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* tests: update output table

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

---------

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>
2026-10-19 10:14:58 +02:00
Johannes Gäßler 4866db350d CUDA: fix warning for nonexistent extra semicolon (#30774) 2026-10-19 10:08:21 +02:00
Aaron Teo 42b70032b1 ggml-cpu: vectorize ggml_v_silu and ggml_v_expf (#30791)
Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>
2026-10-19 09:59:49 +02:00
14 changed files with 2342 additions and 1261 deletions
+10 -1
View File
@@ -20,7 +20,11 @@ on:
paths: [
'.github/workflows/build-ibm.yml',
'ggml/src/ggml-cpu/**',
'ggml/src/ggml-zdnn/**'
'ggml/src/ggml-zdnn/**',
'ggml/src/ggml-quants.c',
'src/llama-quant.cpp',
'gguf-py/gguf/scripts/gguf_convert_endian.py',
'tests/test-quantize-be.sh'
]
cache-mode: none
@@ -105,6 +109,11 @@ jobs:
wget https://huggingface.co/ggml-org/models/resolve/main/tinyllamas/stories260K-be.gguf
./bin/llama-completion -m stories260K-be.gguf -p "One day, Lily met a Shoggoth" -n 500 -c 256
- name: Test quantize (s390x)
id: quantize_test_s390x
run: |
./tests/test-quantize-be.sh ./build/bin/llama-quantize ./build/bin/llama-perplexity 2>&1
ubuntu-26-zdnn-s390x:
name: ubuntu-26-zdnn-s390x
runs-on: ubuntu-24.04-s390x
+8 -4
View File
@@ -2455,9 +2455,10 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
#ifdef ASCEND_310P
// Q4 && Q8 per group is not support on 310p device
return false;
#endif
#else
// only support contiguous for quantized types.
return ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]);
#endif
default:
return false;
}
@@ -2472,9 +2473,10 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
#ifdef ASCEND_310P
// Q4 && Q8 per group is not support on 310p device
return false;
#endif
#else
// only support contiguous for quantized types.
return ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1]);
#endif
default:
return false;
}
@@ -2641,7 +2643,7 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
#ifdef ASCEND_310P
// Ger is not supported on 310p device
return false;
#endif
#else
switch (op->src[0]->type) {
case GGML_TYPE_F16:
case GGML_TYPE_F32:
@@ -2649,6 +2651,7 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
default:
return false;
}
#endif
}
case GGML_OP_CONV_TRANSPOSE_1D:
return true;
@@ -2667,7 +2670,7 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
#ifdef ASCEND_310P
// FA not support on 310p device
return false;
#endif
#else
// derived from [ggml-cuda.cu]
if (op->src[1]->type != GGML_TYPE_F16 || op->src[2]->type != GGML_TYPE_F16) {
return false;
@@ -2693,6 +2696,7 @@ static bool ggml_backend_cann_supports_op(ggml_backend_dev_t dev, const ggml_ten
return false;
}
return true;
#endif
}
case GGML_OP_SSM_CONV:
return true;
+14
View File
@@ -401,6 +401,10 @@ void ggml_vec_silu_f32(const int n, float * y, const float * x) {
for (; i + 3 < n; i += 4) {
vst1q_f32(y + i, ggml_v_silu(vld1q_f32(x + i)));
}
#elif defined(__VXE__) || defined(__VXE2__)
for (; i + 3 < n; i += 4) {
vec_xst(ggml_v_silu(vec_xl(0, x + i)), 0, y + i);
}
#elif defined(__riscv_v_intrinsic)
for (int vl; i < n; i += vl) {
vl = __riscv_vsetvl_e32m2(n - i);
@@ -438,6 +442,10 @@ void ggml_vec_swiglu_f32(const int n, float * y, const float * x, const float *
for (; i + 3 < n; i += 4) {
vst1q_f32(y + i, vmulq_f32(ggml_v_silu(vld1q_f32(x + i)), vld1q_f32(g + i)));
}
#elif defined(__VXE__) || defined(__VXE2__)
for (; i + 3 < n; i += 4) {
vec_xst(vec_mul(ggml_v_silu(vec_xl(0, x + i)), vec_xl(0, g + i)), 0, y + i);
}
#elif defined(__riscv_v_intrinsic)
for (int vl; i < n; i += vl) {
vl = __riscv_vsetvl_e32m2(n - i);
@@ -581,6 +589,12 @@ ggml_float ggml_vec_soft_max_f32(const int n, float * y, const float * x, float
vst1q_f32(y + i, val);
sum += (ggml_float)vaddvq_f32(val);
}
#elif defined(__VXE__) || defined(__VXE2__)
for (; i + 3 < n; i += 4) {
float32x4_t v_val = ggml_v_expf(vec_sub(vec_xl(0, x + i), vec_splats(max)));
vec_xst(v_val, 0, y + i);
sum += (ggml_float)vec_hsum_f32x4(v_val);
}
#elif defined(__riscv_v_intrinsic)
vfloat64m1_t vsum = __riscv_vfmv_v_f_f64m1(0, 1);
for (int avl; i < n; i += avl) {
+41 -1
View File
@@ -1334,6 +1334,46 @@ inline static __m128 ggml_v_silu(__m128 x) {
return _mm_div_ps(x, one_plus_exp_neg_x);
}
#elif defined(__VXE__) || defined(__VXE2__)
// adapted from arm limited optimized routine
// the maximum error is 1.45358 plus 0.5 ulps
// numbers above 88.38 will flush to infinity
// numbers beneath -103.97 will flush to zero
inline static float32x4_t ggml_v_expf(float32x4_t v_x) {
const float32x4_t v_r = vec_splats(0x1.8p23f);
const float32x4_t v_z = vec_madd(v_x, vec_splats(0x1.715476p+0f), v_r);
const float32x4_t v_n = vec_sub(v_z, v_r);
const float32x4_t v_b = vec_nmsub(v_n, vec_splats(0x1.7f7d1cp-20f),
vec_nmsub(v_n, vec_splats(0x1.62e4p-1f), v_x));
const uint32x4_t v_e = vec_sl((uint32x4_t)v_z, 23);
const float32x4_t v_k = (float32x4_t)vec_add(v_e, (uint32x4_t)vec_splats(1.0f));
const uint32x4_t v_c = (uint32x4_t)vec_cmpgt(vec_abs(v_n), vec_splats(126.0f));
const float32x4_t v_u = vec_mul(v_b, v_b);
const float32x4_t v_j = vec_madd(
vec_madd(vec_madd(vec_splats(0x1.0e4020p-7f), v_b, vec_splats(0x1.573e2ep-5f)), v_u,
vec_madd(vec_splats(0x1.555e66p-3f), v_b, vec_splats(0x1.fffdb6p-2f))),
v_u, vec_mul(vec_splats(0x1.ffffecp-1f), v_b));
if (vec_all_eq(v_c, vec_splats(UINT32_C(0)))) {
return vec_madd(v_j, v_k, v_k);
}
const uint32x4_t v_d = vec_and((uint32x4_t)vec_cmple(v_n, vec_splats(0.0f)), vec_splats(UINT32_C(0x82000000)));
const float32x4_t v_s1 = (float32x4_t)vec_add(v_d, vec_splats(UINT32_C(0x7f000000)));
const float32x4_t v_s2 = (float32x4_t)vec_sub(v_e, v_d);
return vec_sel(vec_sel(vec_madd(v_k, v_j, v_k), vec_mul(vec_madd(v_s2, v_j, v_s2), v_s1), v_c),
vec_mul(v_s1, v_s1), (uint32x4_t)vec_cmpgt(vec_abs(v_n), vec_splats(192.0f)));
}
// computes silu x/(1+exp(-x)) in single precision vector
inline static float32x4_t ggml_v_silu(float32x4_t v_x) {
const float32x4_t v_one = vec_splats(1.0f);
const float32x4_t v_zero = vec_splats(0.0f);
const float32x4_t v_neg_x = vec_sub(v_zero, v_x);
const float32x4_t v_exp_neg_x = ggml_v_expf(v_neg_x);
const float32x4_t v_one_plus_exp_neg_x = vec_add(v_one, v_exp_neg_x);
return vec_div(v_x, v_one_plus_exp_neg_x);
}
#elif defined(__riscv_v_intrinsic)
// adapted from arm limited optimized routine
@@ -1386,7 +1426,7 @@ inline static vfloat32m2_t ggml_v_silu_m2(vfloat32m2_t x, int vl) {
return __riscv_vfdiv_vv_f32m2(x, one_plus_exp_neg_x, vl);
}
#endif // __ARM_NEON / __AVX2__ / __SSE2__ / __riscv_v_intrinsic
#endif // __ARM_NEON / __AVX2__ / __SSE2__ / __VXE__ / __riscv_v_intrinsic
inline static void ggml_vec_silu_f16(const int n, ggml_fp16_t * y, const ggml_fp16_t * x) {
for (int i = 0; i < n; ++i) {
+2 -1
View File
@@ -4,7 +4,7 @@
#include <climits>
#include <mutex>
namespace {
namespace ggml_cuda_gated_delta_net_mma {
constexpr int D = 128;
using bf16 = nv_bfloat16;
@@ -417,6 +417,7 @@ static gdn_path select_gdn_mma_path(int device, const ggml_cuda_gdn_mma_args & a
#endif // GGML_USE_HIP
}
} // namespace
using namespace ggml_cuda_gated_delta_net_mma;
bool ggml_cuda_gdn_mma_launch(int device, const ggml_cuda_gdn_mma_args & a, cudaStream_t stream) {
if (!a.state_out) {
@@ -636,6 +636,11 @@ static bool ggml_metal_fusion_check_moe_reduce(
if (!match.experts->data || !match.weights->data || !match.dst->data) {
return false;
}
// the fused kernel reads experts and weights while writing dst, so dst must not overlap either input
if (ggml_metal_fusion_overlap(match.dst, match.experts) || ggml_metal_fusion_overlap(match.dst, match.weights)) {
return false;
}
}
return true;
+1 -1
View File
@@ -5,7 +5,7 @@ import os
import sys
import subprocess
HTTPLIB_VERSION = "refs/tags/v0.61.0"
HTTPLIB_VERSION = "refs/tags/v0.62.0"
# used by examples/gguf-hash, these repos have no release tag, so we pin a commit
XXHASH_COMMIT = "9f465f1ea932d6ad9a26cd77496311ffa544cd68"
+59 -25
View File
@@ -12,19 +12,21 @@
namespace {
// LRU of the experts of a group of layers, the slot of each expert is kept in the slot map of its layer
struct moe_cache_lru {
// SLRU of the experts of a group of layers, the slot of each expert is kept in the slot map of its layer
struct moe_cache_slru {
int32_t n_expert = 0;
int32_t n_slots = 0;
std::vector<int32_t *> slot_map; // [n_layer] data of the slot maps, -1 if the expert is not cached
std::vector<int32_t> key_of; // [n_slots] il*n_expert + expert, -1 if empty
// doubly linked list of the slots, head is the least recently used
// probation and protected LRU lists; hits promote slots to the protected half
std::vector<int32_t> prev;
std::vector<int32_t> next;
int32_t head = -1;
int32_t tail = -1;
int32_t head[2] = { -1, -1 };
int32_t tail[2] = { -1, -1 };
std::vector<bool> is_protected;
int32_t n_protected = 0;
std::vector<uint32_t> seen; // [n_expert]
uint32_t seen_gen = 0;
@@ -41,27 +43,59 @@ struct moe_cache_lru {
prev[s] = s - 1;
next[s] = s + 1 < n_slots ? s + 1 : -1;
}
head = 0;
tail = n_slots - 1;
head[0] = 0;
tail[0] = n_slots - 1;
is_protected.assign(n_slots, false);
seen.assign(n_expert, 0);
}
// move slot s to the tail (most recently used)
void touch(int32_t s) {
if (s == tail) {
// move slot s to the most recently used end of a segment
void move(int32_t s, bool protect) {
const bool from = is_protected[s];
if (from == protect && s == tail[from]) {
return;
}
if (prev[s] >= 0) {
next[prev[s]] = next[s];
} else {
head = next[s];
head[from] = next[s];
}
if (next[s] >= 0) {
prev[next[s]] = prev[s];
} else {
tail[from] = prev[s];
}
prev[next[s]] = prev[s];
prev[s] = tail;
prev[s] = tail[protect];
next[s] = -1;
next[tail] = s;
tail = s;
if (tail[protect] >= 0) {
next[tail[protect]] = s;
} else {
head[protect] = s;
}
tail[protect] = s;
is_protected[s] = protect;
n_protected += int(protect) - int(from);
}
void touch(int32_t s) {
move(s, true);
if (n_protected > n_slots/2) {
move(head[1], false);
}
}
int32_t victim(int32_t il) const {
for (int segment = 0; segment < 2; ++segment) {
for (int32_t s = head[segment]; s >= 0; s = next[s]) {
const int32_t key = key_of[s];
// keep every expert selected by this request, including earlier fills
if (key < 0 || key / n_expert != il || seen[key % n_expert] != seen_gen) {
return s;
}
}
}
GGML_ABORT("no SLRU victim");
}
struct fill {
@@ -69,7 +103,7 @@ struct moe_cache_lru {
int32_t slot;
};
// give a slot to each expert selected by ids in layer il, the misses evict the least recently used experts
// give a slot to each expert selected by ids in layer il, evicting from probation first
// returns false if the ids select more distinct experts than there are slots
bool plan(int32_t il, const int32_t * ids, size_t n_ids, std::vector<fill> & fills, size_t & n_hit) {
fills.clear();
@@ -93,7 +127,7 @@ struct moe_cache_lru {
int32_t * slots = slot_map[il];
// hits go to the tail first, so the head can be evicted below
// promote hits before choosing victims for misses
for (int32_t e : uniq) {
if (slots[e] >= 0) {
touch(slots[e]);
@@ -106,13 +140,13 @@ struct moe_cache_lru {
if (slots[e] >= 0) {
continue;
}
const int32_t s = head;
const int32_t s = victim(il);
if (key_of[s] >= 0) {
slot_map[key_of[s] / n_expert][key_of[s] % n_expert] = -1;
}
key_of[s] = il*n_expert + e;
slots[e] = s;
touch(s);
move(s, false);
fills.push_back({ e, s });
}
return true;
@@ -172,7 +206,7 @@ struct llama_moe_cache::impl {
std::vector<ggml_tensor *> banks; // device storage of all slots, one per expert tensor
size_t host_bytes = 0;
int32_t n_slots = 0;
moe_cache_lru lru;
moe_cache_slru slru;
};
struct layer {
@@ -208,7 +242,7 @@ struct llama_moe_cache::impl {
std::unordered_map<const ggml_tensor *, int32_t> layer_of; // slot map -> layer
std::vector<int32_t> ids;
std::vector<moe_cache_lru::fill> fills;
std::vector<moe_cache_slru::fill> fills;
// slot maps in host memory
ggml_context_ptr ctx_host;
@@ -323,7 +357,7 @@ struct llama_moe_cache::impl {
g.n_slots = 0;
continue;
}
g.lru.init(model.layers.size(), n_expert, g.n_slots);
g.slru.init(model.layers.size(), n_expert, g.n_slots);
n_tensors[g.id] += g.ref.size()*(1 + g.layers.size());
n_tensors_host += g.layers.size();
}
@@ -421,7 +455,7 @@ struct llama_moe_cache::impl {
for (group & g : groups) {
for (int32_t il : g.layers) {
if (layers[il].slot_map != nullptr) {
g.lru.slot_map[il] = (int32_t *) layers[il].slot_map->data;
g.slru.slot_map[il] = (int32_t *) layers[il].slot_map->data;
}
}
}
@@ -489,7 +523,7 @@ struct llama_moe_cache::impl {
}
// large batches only read the cache, so the experts used in generation stay in it
const int32_t * slots = g.lru.slot_map[b.il];
const int32_t * slots = g.slru.slot_map[b.il];
if (slots == nullptr || slots[e] < 0) {
return 0;
}
@@ -550,7 +584,7 @@ struct llama_moe_cache::impl {
ggml_backend_synchronize(backend);
size_t n_hit = 0;
if (!g.lru.plan(il, ids.data(), ids.size(), fills, n_hit)) {
if (!g.slru.plan(il, ids.data(), ids.size(), fills, n_hit)) {
GGML_ABORT("the MoE cache is too small for the experts selected in layer %d", il);
}
+1 -1
View File
@@ -8,7 +8,7 @@
struct llama_model;
// keeps the most recently used experts of host-resident MoE layers in a device buffer
// keeps recently and repeatedly used experts of host-resident MoE layers in a device buffer
// each layer has a slot map in host memory: when the scheduler copies it to the device, the copy callback uploads the missing experts
class llama_moe_cache {
public:
+178
View File
@@ -0,0 +1,178 @@
#!/usr/bin/env bash
set -euo pipefail
if [ $# -lt 2 ]; then
printf "Usage: %s <llama-quantize> <llama-perplexity> [<scratch-dir>] [--no-check]\n" "$0"
printf " <scratch-dir> dir for the model and outputs, default /tmp/test-quantize-be\n"
printf " --no-check print the perplexity of each output without checking it\n"
printf "Example: %s ./build/bin/llama-quantize ./build/bin/llama-perplexity\n" "$0"
exit 1
fi
quantize=$1
perplexity=$2
shift 2
scratch_dir=/tmp/test-quantize-be
no_check=
for arg in "$@"; do
case $arg in
--no-check) no_check=--no-check ;;
*) scratch_dir=$arg ;;
esac
done
for bin in "$quantize" "$perplexity"; do
if [ ! -x "$bin" ]; then
echo "error: $bin not found, build it first" >&2
exit 1
fi
done
if [ "$(uname -m)" != s390x ]; then
echo "error: this test needs a big-endian s390x host" >&2
exit 1
fi
MODEL_URL=https://huggingface.co/ggml-org/models/resolve/499bc8821c6b12b4e53c5bffcb21ec206f212d81/tinyllamas/stories15M.gguf
MODEL_SHA256=61b50d457809a5194818fd22e6724b456cd7bb9a6264c52c8110684c53f3704a
F32_SHA256=55174d7a7d46f29fc6be163ccb040330c357aa727b22ec13e2c5d713e5ba5b76
# perplexity ceiling of each big-endian output on the text below, about 10% above the measured value
# a wrong byte order gives nan or a perplexity in the hundreds
# F32 is the converted input, all other types come from llama-quantize --pure
declare -a outputs=(
"F32 3.60"
"F16 3.60"
"BF16 3.59"
"Q4_0 4.14"
"Q4_1 4.01"
"Q5_0 3.82"
"Q5_1 3.65"
"Q8_0 3.60"
"Q2_K 5.24"
"Q3_K 4.25"
"Q4_K 3.83"
"Q5_K 3.67"
"Q6_K 3.60"
"IQ4_NL 3.86"
"IQ4_XS 3.85"
"MXFP4_MOE 4.27"
)
check_sha256() {
local name=$1
local file=$2
local expected=$3
local actual
actual=$(sha256sum "$file" | cut -d ' ' -f 1)
if [ "$actual" != "$expected" ]; then
echo "error: sha256 mismatch for $name: expected '$expected', got '$actual'" >&2
exit 1
fi
}
# keep the report readable, only show a command's output when it fails
run_logged() {
local log=$1
shift
if ! "$@" > "$log" 2>&1; then
cat "$log" >&2
echo "error: command failed: $*" >&2
return 1
fi
}
# quantize one type and print its perplexity, print nothing if a step fails
measure_ppl() {
local type=$1
local out=$2
local log=$scratch_dir/stories15M-be-$type-ppl.log
if [ "$type" != F32 ]; then
run_logged "$scratch_dir/stories15M-be-$type.log" "$quantize" --pure "$f32" "$out" "$type" || return 1
fi
run_logged "$log" "$perplexity" -m "$out" -f "$text" -c 128 || return 1
# llama-perplexity prints no final estimate when the result is nan
sed -n 's/.*Final estimate: PPL = \([0-9.]*\).*/\1/p' "$log"
}
mkdir -p "$scratch_dir"
model=$scratch_dir/stories15M.gguf
if [ ! -f "$model" ]; then
wget -q -O "$model" "$MODEL_URL"
fi
check_sha256 stories15M.gguf "$model" "$MODEL_SHA256"
f32=$scratch_dir/stories15M-be-F32.gguf
cp "$model" "$f32"
echo YES | run_logged "$scratch_dir/stories15M-be-F32.log" python3 "$(dirname "$0")/../gguf-py/gguf/scripts/gguf_convert_endian.py" "$f32" big || exit 1
check_sha256 F32 "$f32" "$F32_SHA256"
# 588 tokens, enough for 4 chunks of 128
text=$scratch_dir/stories.txt
cat > "$text" << 'EOF'
Once upon a time, there was a little girl named Lily. She lived in a small house with her mom and dad. Lily loved to play outside in the garden. One sunny day, she saw a big red ball under a tree. She ran to the ball and picked it up. It was soft and round.
Lily wanted to play with her friend Tom. She went to his house and knocked on the door. Tom opened the door and smiled. "Hi, Lily! Do you want to play?" he asked. "Yes! Look at my new ball," said Lily. They went to the park together.
At the park, Lily and Tom threw the ball to each other. They laughed and ran on the green grass. Then the ball went too high. It flew over a fence and into a pond. Lily was sad. "Oh no, my ball is in the water," she said.
Tom had an idea. He found a long stick near the pond. He held the stick out over the water and pushed the ball to the side. Lily helped him. Soon the ball came close and Lily picked it up. It was wet, but it was not broken.
"Thank you, Tom," said Lily. "You are a good friend." Tom smiled and said, "You are welcome. Let's play again, but not near the pond." They both laughed and went back to the grass.
When the sun went down, Lily's mom called her home. Lily said goodbye to Tom and walked home with her ball. She told her mom about the pond and the stick. Her mom gave her a big hug. "I am happy you and Tom helped each other," she said.
That night, Lily put the ball next to her bed. She thought about the fun day at the park. She closed her eyes and smiled. Lily knew that sharing and helping made every day better.
The next morning, Lily woke up early. She saw a little bird outside her window. The bird was singing a happy song. Lily wanted to give the bird some food. She asked her dad for some bread. Her dad gave her a small piece, and Lily put it on the window.
The bird hopped over and ate the bread. Then it sang again, louder than before. Lily clapped her hands. "Thank you for the song, little bird," she said. The bird flew away, but it came back every morning after that.
Lily and the bird became good friends. Every day she gave it bread, and every day it sang for her. Lily learned that being kind to others, even small birds, can make the world a happy place.
EOF
rows=()
n_ok=0
n_bad=0
for entry in "${outputs[@]}"; do
read -r type max <<< "$entry"
out=$scratch_dir/stories15M-be-$type.gguf
if ! ppl=$(measure_ppl "$type" "$out"); then
ppl=-
status=FAIL
elif [ "$no_check" = --no-check ]; then
status=-
elif [[ $ppl =~ ^[0-9.]+$ ]] && awk -v p="$ppl" -v m="$max" 'BEGIN { exit !(p <= m) }'; then
status=OK
else
status=FAIL
fi
case $status in
OK) n_ok=$((n_ok + 1)) ;;
FAIL) n_bad=$((n_bad + 1)) ;;
esac
rows+=("$type ${ppl:-nan} $max $status")
printf "%s: %-10s (%s) done\n" "$(basename "$0")" "$type" "$out"
done
printf "\n%-10s %10s %10s %s\n" type ppl max status
for row in "${rows[@]}"; do
read -r type ppl max status <<< "$row"
case $status in
OK) status="\033[32mOK\033[0m" ;;
FAIL) status="\033[31mFAIL\033[0m" ;;
esac
printf "%-10s %10s %10s %b\n" "$type" "$ppl" "$max" "$status"
done
if [ "$no_check" != --no-check ]; then
printf "summary: %d ok, %d failed\n" "$n_ok" "$n_bad"
fi
if [ "$n_bad" -ne 0 ]; then
printf "\nif the perplexity is expected to change, run with --no-check and update the max values in %s:\n\n" "$0"
printf "%s %s %s %s --no-check\n" "$0" "$quantize" "$perplexity" "$scratch_dir"
exit 1
fi
+5 -2
View File
@@ -3781,6 +3781,9 @@ private:
llama_pos pos_next = slot.prompt.tokens.pos_next(n_past);
// number of cached tokens shared with the new prompt, before any checkpoint is restored
const int n_prefix = n_past;
// ref: https://github.com/ggml-org/llama.cpp/pull/24110
const bool has_new_tokens = (n_past < slot.task->n_tokens());
@@ -3891,10 +3894,10 @@ private:
}
{
// erase any checkpoints with pos_max > pos_next
// erase any checkpoints with pos_max > pos_next, or that cover tokens not shared with the new prompt
for (auto it = slot.prompt.checkpoints.begin(); it != slot.prompt.checkpoints.end();) {
const auto & cur = *it;
if (cur.pos_max > pos_next) {
if (cur.pos_max > pos_next || cur.n_tokens > n_prefix) {
SLT_TRC(slot, "erased invalidated context checkpoint (pos_min = %d, pos_max = %d, n_tokens = %" PRId64 ", n_swa = %d, pos_next = %d, size = %.3f MiB)\n", cur.pos_min, cur.pos_max, cur.n_tokens, n_swa, pos_next, (float) cur.size() / 1024 / 1024);
it = slot.prompt.checkpoints.erase(it);
} else {
@@ -689,3 +689,81 @@ def test_completion_prompt_cache():
assert "prompt_n" in timings and timings["prompt_n"] + timings["cache_n"] == n_prompt
assert "predicted_n" in timings and timings["predicted_n"] == n_predict
assert "tokens" in res.body and isinstance(res.body["tokens"], list)
# a slot that switched to an unrelated prompt must not restore a context checkpoint of the previous one
def test_completion_checkpoint_after_unrelated_prompt():
global server
server = ServerPreset.tinygemma3()
server.n_slots = 1
server.cache_ram = 0
server.temperature = 0.0
server.start()
def tokenize(content):
res = server.make_request("POST", "/tokenize", data={"content": content})
assert res.status_code == 200
return res.body["tokens"]
def top_logprobs(prompt, cache_prompt):
res = server.make_request("POST", "/completion", data={
"prompt": prompt,
"n_predict": 1,
"n_probs": 5,
"cache_prompt": cache_prompt,
})
assert res.status_code == 200
return [(p["id"], p["logprob"]) for p in res.body["completion_probabilities"][0]["top_logprobs"]]
tokens = tokenize("Purple elephants quietly compose sonnets about tax law")
expected = top_logprobs(tokens[:2], False)
# 5 tokens: the checkpoint 4 tokens before the end of the prompt holds only its first token
top_logprobs(tokenize("The quick brown fox jumps over the lazy dog")[:5], True)
top_logprobs(tokens[:1], True)
actual = top_logprobs(tokens[:2], True)
assert [tid for tid, _ in actual] == [tid for tid, _ in expected]
for (_, lp), (_, lp_expected) in zip(actual, expected):
assert lp == pytest.approx(lp_expected, abs=1e-3)
# a restored context checkpoint must be kept for the next request that shares the same prefix
def test_completion_checkpoint_kept_after_restore():
global server
server = ServerPreset.tinygemma3()
server.n_slots = 1
server.n_batch = 16
server.n_ubatch = 16
server.cache_ram = 0
server.temperature = 0.0
server.start()
def tokenize(content):
res = server.make_request("POST", "/tokenize", data={"content": content})
assert res.status_code == 200
return res.body["tokens"]
def cache_n(prompt):
res = server.make_request("POST", "/completion", data={
"prompt": prompt,
"n_predict": 1,
"cache_prompt": True,
})
assert res.status_code == 200
return res.body["timings"]["cache_n"]
prefix = tokenize("Purple elephants quietly compose sonnets about tax law in the moonlight")
suffixes = [tokenize(s) for s in [
"The quick brown fox jumps over the lazy dog while the farmer counts his sheep and the sun slowly sets behind the distant hills",
"Seven tiny robots repaired the broken lighthouse lens before the storm arrived, then danced on the rocks until the morning tide came in",
"Green apples rolled down the hill toward the river, where a curious otter collected them one by one and stacked them on a flat stone",
]]
# 4 tokens: the checkpoint 4 tokens before the end of the prompt holds exactly the prefix
cache_n(prefix + suffixes[0][:4])
# suffixes longer than n_ubatch + 4: no new checkpoint within the prefix, only the restored one can be reused
for suffix in suffixes[1:]:
assert len(suffix) > server.n_ubatch + 4
assert cache_n(prefix + suffix) == len(prefix) - 1
+1787 -1196
View File
File diff suppressed because it is too large Load Diff
+153 -29
View File
@@ -8,8 +8,8 @@
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION "0.61.0"
#define CPPHTTPLIB_VERSION_NUM "0x003d00"
#define CPPHTTPLIB_VERSION "0.62.0"
#define CPPHTTPLIB_VERSION_NUM "0x003e00"
#ifdef _WIN32
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00
@@ -18,6 +18,14 @@
#endif
#endif
#if defined(__SANITIZE_THREAD__)
#define CPPHTTPLIB_THREAD_SANITIZER
#elif defined(__has_feature)
#if __has_feature(thread_sanitizer)
#define CPPHTTPLIB_THREAD_SANITIZER
#endif
#endif
/*
* Configuration
*/
@@ -122,6 +130,10 @@
#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20
#endif
#ifndef CPPHTTPLIB_INTERIM_RESPONSE_MAX_COUNT
#define CPPHTTPLIB_INTERIM_RESPONSE_MAX_COUNT 100
#endif
#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT
#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024
#endif
@@ -130,6 +142,10 @@
#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (100 * 1024 * 1024) // 100MB
#endif
#ifndef CPPHTTPLIB_SSE_MAX_EVENT_LENGTH
#define CPPHTTPLIB_SSE_MAX_EVENT_LENGTH (16 * 1024 * 1024) // 16MB
#endif
#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH
#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192
#endif
@@ -211,6 +227,16 @@
#define CPPHTTPLIB_MAX_LINE_LENGTH 32768
#endif
// These bound the parsing of every request, so 0 cannot lift them.
static_assert(CPPHTTPLIB_REQUEST_URI_MAX_LENGTH > 0,
"CPPHTTPLIB_REQUEST_URI_MAX_LENGTH must be positive");
static_assert(CPPHTTPLIB_HEADER_MAX_LENGTH > 0,
"CPPHTTPLIB_HEADER_MAX_LENGTH must be positive");
static_assert(CPPHTTPLIB_HEADER_MAX_COUNT > 0,
"CPPHTTPLIB_HEADER_MAX_COUNT must be positive");
static_assert(CPPHTTPLIB_MAX_LINE_LENGTH > 0,
"CPPHTTPLIB_MAX_LINE_LENGTH must be positive");
#ifndef CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH
#define CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH 16777216
#endif
@@ -340,7 +366,6 @@ using socklen_t = int;
#include <netinet/tcp.h>
#include <poll.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
@@ -451,6 +476,7 @@ using socket_t = int;
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
@@ -581,6 +607,10 @@ namespace ws {
class WebSocket;
} // namespace ws
namespace sse {
class SSEClient;
} // namespace sse
namespace detail {
/*
@@ -660,6 +690,15 @@ inline bool equal(const std::string &a, const std::string &b) {
});
}
// For a literal, without building a std::string from it.
template <size_t N>
inline bool equal(const std::string &a, const char (&b)[N]) {
return a.size() == N - 1 &&
std::equal(a.begin(), a.end(), b, [](char ca, char cb) {
return to_lower(ca) == to_lower(cb);
});
}
struct equal_to {
bool operator()(const std::string &a, const std::string &b) const {
return equal(a, b);
@@ -668,17 +707,13 @@ struct equal_to {
struct hash {
size_t operator()(const std::string &key) const {
return hash_core(key.data(), key.size(), 0);
}
size_t hash_core(const char *s, size_t l, size_t h) const {
return (l == 0) ? h
: hash_core(s + 1, l - 1,
// Unsets the 6 high bits of h, therefore no
// overflow happens
(((std::numeric_limits<size_t>::max)() >> 6) &
h * 33) ^
static_cast<unsigned char>(to_lower(*s)));
size_t h = 0;
for (auto c : key) {
// Unsets the 6 high bits of h, therefore no overflow happens
h = (((std::numeric_limits<size_t>::max)() >> 6) & h * 33) ^
static_cast<unsigned char>(to_lower(c));
}
return h;
}
};
@@ -736,6 +771,7 @@ inline from_chars_result<T> from_chars(const char *first, const char *last,
}
if (p == last) { return {first, std::errc::invalid_argument}; }
auto radix = static_cast<T>(base);
T result = 0;
for (; p != last; ++p) {
char c = *p;
@@ -751,10 +787,11 @@ inline from_chars_result<T> from_chars(const char *first, const char *last,
}
if (digit < 0 || digit >= base) { break; }
if (result > ((std::numeric_limits<T>::max)() - digit) / base) {
auto d = static_cast<T>(digit);
if (result > ((std::numeric_limits<T>::max)() - d) / radix) {
return {p, std::errc::result_out_of_range};
}
result = result * base + digit;
result = result * radix + d;
}
if (p == first || (negative && p == first + 1)) {
@@ -881,6 +918,12 @@ inline bool parse_url(const std::string &url, UrlComponents &uc) {
auto has_authority = has_authority_prefix || (!url.empty() && url[0] != '/' &&
url[0] != '?' && url[0] != '#');
if (has_authority) {
// userinfo is not supported, and a backslash that some parsers take for
// '/' would leave them seeing another host than this one.
if (url.find_first_of("@\\", pos) < url.find_first_of("/?#", pos)) {
return false;
}
if (pos < url.size() && url[pos] == '[') {
auto close = url.find(']', pos);
if (close == std::string::npos) { return false; }
@@ -1803,6 +1846,7 @@ struct Request {
bool is_multipart_form_data() const;
// private members...
bool body_read_started_ = false;
bool body_consumed_ = false;
bool expect_100_continue_pending_ = false;
size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT;
@@ -1972,6 +2016,16 @@ public:
(void)usec;
}
// Bounds the reads from now on to `msec` in total, on top of the timeout of
// each one. 0 lifts the bound.
virtual void set_max_timeout(time_t msec) { (void)msec; }
// Lets a wait for data end early once `interrupted` returns true, so that
// another thread can stop a blocked read on every platform.
virtual void set_read_interrupt(std::function<bool()> interrupted) {
(void)interrupted;
}
// Bytes already pulled off the socket and sitting in this stream's own
// buffer. Exposing them lets a line reader scan for a terminator in one
// pass instead of asking for a byte at a time. A stream that does no
@@ -2425,7 +2479,8 @@ private:
int bind_internal(const std::string &host, int port, int socket_flags);
bool listen_internal();
bool routing(Request &req, Response &res, Stream &strm);
bool routing(Request &req, Response &res, Stream &strm,
bool skip_pre_routing);
bool handle_file_request(Request &req, Response &res);
bool check_if_not_modified(const Request &req, Response &res,
const std::string &etag, time_t mtime) const;
@@ -2724,6 +2779,7 @@ public:
private:
friend class ClientImpl;
friend class sse::SSEClient;
ssize_t read_with_decompression(char *buf, size_t len);
@@ -2941,14 +2997,13 @@ protected:
void close_socket(Socket &socket);
void disconnect(bool gracefully);
std::string make_request_target(const Request &req) const;
bool process_request(Stream &strm, Request &req, Response &res,
bool close_connection, Error &error);
bool write_content_with_provider(Stream &strm, const Request &req,
Error &error) const;
void copy_settings(const ClientImpl &rhs);
void output_log(const Request &req, const Response &res) const;
void output_error_log(const Error &err, const Request *req) const;
@@ -2960,6 +3015,9 @@ protected:
Socket socket_;
mutable std::mutex socket_mutex_;
std::recursive_mutex request_mutex_;
// How many send() calls are running on this client, guarded by
// request_mutex_
size_t send_depth_ = 0;
// These are all protected under socket_mutex
size_t socket_requests_in_flight_ = 0;
@@ -3071,6 +3129,9 @@ private:
virtual bool is_ssl() const;
void transfer_socket_ownership_to_handle(StreamHandle &handle);
StreamHandle open_stream_(Request &req);
void add_credential_headers(Request &req);
std::string proxy_target_prefix() const;
#ifdef CPPHTTPLIB_SSL_ENABLED
public:
@@ -3100,6 +3161,24 @@ protected:
#endif
};
namespace detail {
// Stands in for a client that Client could not make from its arguments, so a
// request fails rather than going through a null pointer.
class InvalidClientImpl final : public ClientImpl {
public:
InvalidClientImpl() : ClientImpl(std::string(), 0) {}
bool is_valid() const override { return false; }
private:
bool create_and_connect_socket(Socket &, Error &error) override {
error = Error::Connection;
return false;
}
};
} // namespace detail
class Client {
public:
// Universal interface
@@ -3364,7 +3443,14 @@ private:
bool process_and_close_socket(socket_t sock) override;
tls::ctx_t ctx_ = nullptr;
// update_certs_pem() replaces the certificates and CAs that handshakes read
// from ctx_, so it waits for the handshakes in progress, which their timeout
// bounds, and new ones wait while it is pending so that they cannot keep it
// waiting.
std::mutex ctx_mutex_;
std::condition_variable ctx_cv_;
size_t handshakes_in_progress_ = 0;
size_t updates_pending_ = 0;
int last_ssl_error_ = 0;
};
@@ -3446,11 +3532,12 @@ private:
std::function<SSLVerifierResponse(tls::session_t)> session_verifier_;
// Like ca_cert_store_set_, keeps what ctx_ cannot report back: the verifier
// set_server_certificate_verifier() installed there.
tls::VerifyCallback server_certificate_verifier_;
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
bool enable_windows_cert_verification_ = true;
// Like ca_cert_store_set_, tracks what ctx_ cannot report back: whether
// set_server_certificate_verifier() installed a verifier.
bool server_certificate_verifier_set_ = false;
#endif
friend class ClientImpl;
@@ -3571,7 +3658,7 @@ inline void ClientImpl::set_max_timeout(
const std::chrono::duration<Rep, Period> &duration) {
auto msec =
std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
set_max_timeout(msec);
set_max_timeout(static_cast<time_t>(msec));
}
template <class Rep, class Period>
@@ -3937,6 +4024,8 @@ struct ChunkedDecoder {
bool parse_trailers_into(Headers &dest, const Headers &src_headers);
};
// A regular file opened to be served: mapped on Windows, read with pread()
// elsewhere.
class mmap {
public:
mmap(const char *path);
@@ -3947,18 +4036,20 @@ public:
bool is_open() const;
size_t size() const;
const char *data() const;
// Copies up to len bytes from offset into buf and returns how many: fewer,
// down to 0, if the file has shrunk since it was opened.
size_t read(size_t offset, char *buf, size_t len) const;
private:
#if defined(_WIN32)
HANDLE hFile_ = NULL;
HANDLE hMapping_ = NULL;
void *addr_ = nullptr;
bool is_open_empty_file = false;
#else
int fd_ = -1;
#endif
size_t size_ = 0;
void *addr_ = nullptr;
bool is_open_empty_file = false;
};
// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5
@@ -4003,8 +4094,10 @@ struct MbedTlsContext {
mbedtls_x509_crt own_cert;
mbedtls_pk_context own_key;
bool is_server = false;
// Peer certificate required (set_verify_client()); a client defaults to true
bool verify_client = false;
bool has_verify_callback = false;
// Set by tls::set_verify_callback().
VerifyCallback verify_callback;
MbedTlsContext();
~MbedTlsContext();
@@ -4027,8 +4120,10 @@ namespace impl {
struct WolfSSLContext {
WOLFSSL_CTX *ctx = nullptr;
bool is_server = false;
// Peer certificate required (set_verify_client()); a client defaults to true
bool verify_client = false;
bool has_verify_callback = false;
// Replaced under verify_callback_mutex()
std::shared_ptr<VerifyCallback> verify_callback;
std::string ca_pem_data_; // accumulated PEM for get_ca_names/get_ca_certs
WolfSSLContext();
@@ -4453,7 +4548,8 @@ enum ReadResult : int { Fail = 0, Text = 1, Binary = 2, Timeout = 3 };
// Result of WebSocketClient::connect(). Truthy only when the WebSocket
// upgrade handshake fully succeeded. On failure error() identifies the
// failing layer; status()/headers() expose the server's upgrade response
// when one was received (status() is -1 otherwise).
// when one was received (status() is -1 otherwise), or the proxy's reply to
// CONNECT when error() is ProxyConnection.
class Result {
public:
Result() = default;
@@ -4555,6 +4651,8 @@ private:
int max_missed_pongs_;
int unacked_pings_ = 0;
std::atomic<bool> closed_{false};
// Set when the peer stopped answering pings, to end a read() waiting on it
std::atomic<bool> pong_timed_out_{false};
// Set once the caller has bounded read() through set_read_timeout(). Until
// then the timeout in effect is the compile-time default, and elapsing it
// is a failure that closes the connection, not a Timeout.
@@ -4612,6 +4710,12 @@ public:
void set_interface(const std::string &intf);
void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
void set_proxy(const std::string &host, int port);
void set_proxy_basic_auth(const std::string &username,
const std::string &password);
void set_proxy_bearer_token_auth(const std::string &token);
void set_no_proxy(const std::vector<std::string> &patterns);
#ifdef CPPHTTPLIB_SSL_ENABLED
struct PemMemory {
const char *cert_pem;
@@ -4630,13 +4734,23 @@ public:
void enable_server_certificate_verification(bool enabled);
void enable_server_hostname_verification(bool enabled);
void enable_system_ca(bool enabled);
void set_server_certificate_verifier(tls::VerifyCallback verifier);
void set_session_verifier(
std::function<SSLVerifierResponse(tls::session_t)> verifier);
void set_proxy_digest_auth(const std::string &username,
const std::string &password);
#endif
private:
void shutdown_and_close();
std::pair<time_t, time_t> handshake_timeout() const;
bool create_stream(std::unique_ptr<Stream> &strm, Error &error,
int &ssl_error, uint64_t &ssl_backend_error);
void prepare_default_headers(Request &req);
bool is_proxy_enabled() const;
bool create_and_connect_socket(Error &error);
bool send_proxy_connect(Request &req, Response &proxy_res, Error &error);
bool connect_with_proxy(Response &proxy_res, Error &error);
std::string host_;
int port_;
@@ -4666,6 +4780,13 @@ private:
// hostname; only the connection target changes, never the identity.
std::map<std::string, std::string> addr_map_;
std::string proxy_host_;
int proxy_port_ = -1;
std::string proxy_basic_auth_username_;
std::string proxy_basic_auth_password_;
std::string proxy_bearer_token_auth_token_;
std::vector<detail::NoProxyEntry> no_proxy_entries_;
#ifdef CPPHTTPLIB_SSL_ENABLED
bool is_ssl_ = false;
tls::ctx_t tls_ctx_ = nullptr;
@@ -4677,6 +4798,9 @@ private:
SystemCAMode system_ca_mode_ = SystemCAMode::Auto;
bool server_certificate_verification_ = true;
bool server_hostname_verification_ = true;
std::function<SSLVerifierResponse(tls::session_t)> session_verifier_;
std::string proxy_digest_auth_username_;
std::string proxy_digest_auth_password_;
#endif
};