mirror of
https://github.com/LostRuins/koboldcpp.git
synced 2026-09-19 09:15:18 +02:00
Merge branch 'upstream' into concedo_experimental
# Conflicts: # .devops/openvino.Dockerfile # .github/workflows/build-cache.yml # .github/workflows/build-openvino.yml # .github/workflows/build-self-hosted.yml # .github/workflows/release.yml # ci/run.sh # docs/backend/OPENVINO.md # docs/speculative.md # ggml/src/ggml-hexagon/ggml-hexagon.cpp # ggml/src/ggml-hexagon/htp/htp-ops.h # ggml/src/ggml-hexagon/htp/hvx-arith.h # ggml/src/ggml-hexagon/htp/hvx-log.h # ggml/src/ggml-hexagon/htp/main.c # ggml/src/ggml-hexagon/htp/unary-ops.c # ggml/src/ggml-hexagon/htp/unary-ops.h # ggml/src/ggml-opencl/ggml-opencl.cpp # ggml/src/ggml-openvino/CMakeLists.txt # ggml/src/ggml-openvino/ggml-decoder.cpp # ggml/src/ggml-openvino/ggml-decoder.h # ggml/src/ggml-openvino/ggml-openvino-extra.cpp # ggml/src/ggml-openvino/ggml-openvino.cpp # ggml/src/ggml-openvino/openvino/op/cpy.cpp # ggml/src/ggml-openvino/openvino/op/flash_attn_ext.cpp # ggml/src/ggml-openvino/openvino/op/gated_delta_net.cpp # ggml/src/ggml-openvino/openvino/op/view.cpp # ggml/src/ggml-openvino/openvino/op_table.cpp # ggml/src/ggml-openvino/openvino/op_table.h # ggml/src/ggml-openvino/openvino/translate_session.cpp # ggml/src/ggml-openvino/openvino/utils.cpp # ggml/src/ggml-openvino/utils.cpp # ggml/src/ggml-openvino/utils.h # ggml/src/ggml-sycl/fattn-onednn.cpp # ggml/src/ggml-sycl/fattn.cpp # scripts/pr2wt.sh # src/CMakeLists.txt # src/llama-mmap.cpp # src/llama-quant.cpp # tests/CMakeLists.txt # tests/test-arg-parser.cpp # tests/test-backend-ops.cpp # tests/test-llama-archs.cpp # tests/test-save-load-state.cpp # tools/cli/README.md # tools/completion/README.md # tools/server/README.md
This commit is contained in:
+105
-11
@@ -23,6 +23,7 @@
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <random>
|
||||
#include <utility>
|
||||
#include <fstream>
|
||||
|
||||
@@ -51,6 +52,50 @@ static common_speculative_output_limits server_output_limits(const common_params
|
||||
return result;
|
||||
}
|
||||
|
||||
// synthetic draft verification for benchmarking - accept draft tokens at random instead of by match with the target
|
||||
// on replay the draft was already accepted before a context checkpoint restore, so repeat the same decisions
|
||||
static std::vector<llama_token> server_sample_and_accept_synth(
|
||||
common_sampler * smpl,
|
||||
llama_context * ctx,
|
||||
const std::vector<int32_t> & idxs,
|
||||
const llama_tokens & draft,
|
||||
const std::vector<double> & synth_probs,
|
||||
std::mt19937 & rng,
|
||||
bool is_replay) {
|
||||
GGML_ASSERT(idxs.size() == draft.size() + 1);
|
||||
GGML_ASSERT(synth_probs.size() >= draft.size());
|
||||
|
||||
std::vector<llama_token> result;
|
||||
result.reserve(idxs.size());
|
||||
|
||||
const llama_vocab * vocab = llama_model_get_vocab(llama_get_model(ctx));
|
||||
std::uniform_real_distribution<double> dist(0.0, 1.0);
|
||||
for (size_t i = 0; i < draft.size(); ++i) {
|
||||
const llama_token id = common_sampler_sample(smpl, ctx, idxs[i]);
|
||||
const bool accept = is_replay || dist(rng) < synth_probs[i];
|
||||
// do not accept a drafted EOG token - it would end the generation early
|
||||
// on replay the last token is from the target and can be EOG, so skip this check
|
||||
if (accept && (is_replay || !llama_vocab_is_eog(vocab, draft[i]))) {
|
||||
// synthetic draft tokens do not advance grammar or reasoning state
|
||||
// the last replay token is from the target and must advance both
|
||||
const bool is_replay_target = is_replay && i + 1 == draft.size();
|
||||
common_sampler_accept(smpl, draft[i], is_replay_target);
|
||||
result.push_back(draft[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
common_sampler_accept(smpl, id, true);
|
||||
result.push_back(id);
|
||||
return result;
|
||||
}
|
||||
|
||||
const llama_token id = common_sampler_sample(smpl, ctx, idxs[draft.size()]);
|
||||
common_sampler_accept(smpl, id, true);
|
||||
result.push_back(id);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// state diagram: https://github.com/ggml-org/llama.cpp/pull/9283
|
||||
enum slot_state {
|
||||
SLOT_STATE_IDLE,
|
||||
@@ -211,6 +256,7 @@ struct server_slot {
|
||||
std::vector<int32_t> spec_i_batch;
|
||||
common_prompt_checkpoint spec_ckpt;
|
||||
bool spec_is_replay = false;
|
||||
std::mt19937 spec_synth_rng;
|
||||
|
||||
// TODO: move members that belong to the task (such as `generated_text`, `has_new_line`) to task_results_state
|
||||
// see https://github.com/ggml-org/llama.cpp/pull/18283#issuecomment-3710175837
|
||||
@@ -1162,10 +1208,31 @@ private:
|
||||
|
||||
const int n_ctx_train = llama_model_n_ctx_train(model_tgt);
|
||||
|
||||
int n_ctx_slot = llama_n_ctx_seq(ctx_tgt);
|
||||
if (n_ctx_slot > n_ctx_train) {
|
||||
SRV_WRN("the slot context (%d) exceeds the training context of the model (%d) - capping\n", n_ctx_slot, n_ctx_train);
|
||||
n_ctx_slot = n_ctx_train;
|
||||
{
|
||||
// note: the capping itself is done in n_ctx_slot(), here we only report it
|
||||
const int n_ctx_seq = llama_n_ctx_seq(ctx_tgt);
|
||||
|
||||
if (params_base.kv_unified_per_slot > 0) {
|
||||
if (n_ctx_seq > params_base.kv_unified_per_slot) {
|
||||
SRV_INF("capping per-slot context (%d) to --kv-unified-per-slot (%d)\n",
|
||||
n_ctx_seq, params_base.kv_unified_per_slot);
|
||||
} else if (params_base.kv_unified_per_slot > n_ctx_seq) {
|
||||
// cap is above the per-slot pool capacity, so it can never bind
|
||||
SRV_WRN(
|
||||
"--kv-unified-per-slot (%d) exceeds the per-slot pool capacity (%d) - cap has no effect, "
|
||||
"slots are limited to %d (raise the KV pool with -c, or unset -c to size it to "
|
||||
"n_parallel * kv_unified_per_slot)\n",
|
||||
params_base.kv_unified_per_slot, n_ctx_seq, n_ctx_seq);
|
||||
}
|
||||
}
|
||||
|
||||
const int n_ctx_capped = params_base.kv_unified_per_slot > 0 ?
|
||||
std::min(n_ctx_seq, params_base.kv_unified_per_slot) : n_ctx_seq;
|
||||
|
||||
if (n_ctx_capped > n_ctx_train) {
|
||||
SRV_WRN("the slot context (%d) exceeds the training context of the model (%d) - capping\n",
|
||||
n_ctx_capped, n_ctx_train);
|
||||
}
|
||||
}
|
||||
|
||||
slots.clear();
|
||||
@@ -1181,7 +1248,7 @@ private:
|
||||
|
||||
// setup slots
|
||||
SRV_INF("initializing, n_slots = %d, n_ctx_slot = %d, kv_unified = '%s'\n",
|
||||
params_base.n_parallel, n_ctx_slot, params_base.kv_unified ? "true" : "false");
|
||||
params_base.n_parallel, n_ctx_slot(), params_base.kv_unified ? "true" : "false");
|
||||
|
||||
// initialize slots
|
||||
for (int i = 0; i < params_base.n_parallel; i++) {
|
||||
@@ -1194,6 +1261,9 @@ private:
|
||||
spec.reset(common_speculative_init(params_base.speculative, params_base.n_parallel));
|
||||
} catch (const std::exception & e) {
|
||||
SRV_ERR("failed to initialize speculative decoding context: %s\n", e.what());
|
||||
if (params_base.speculative.has_synth()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1209,6 +1279,11 @@ private:
|
||||
model_dft = nullptr;
|
||||
}
|
||||
|
||||
if (!spec && params_base.speculative.has_synth()) {
|
||||
SRV_ERR("%s", "synthetic acceptance requires an initialized speculative decoding context\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < params_base.n_parallel; i++) {
|
||||
server_slot & slot = slots[i];
|
||||
|
||||
@@ -1217,7 +1292,7 @@ private:
|
||||
slot.ctx_dft = ctx_dft;
|
||||
slot.mem.init(ctx_tgt, ctx_dft);
|
||||
slot.spec = spec.get();
|
||||
slot.n_ctx = n_ctx_slot;
|
||||
slot.n_ctx = n_ctx_slot();
|
||||
|
||||
slot.mctx = mctx;
|
||||
slot.prompt.tokens.has_mtmd = mctx != nullptr;
|
||||
@@ -1717,6 +1792,13 @@ private:
|
||||
|
||||
SLT_TRC(slot, "sampler chain: %s\n", common_sampler_print(slot.smpl.get()).c_str());
|
||||
SLT_TRC(slot, "sampler params: \n%s\n", task.params.sampling.print().c_str());
|
||||
|
||||
if (spec && !common_speculative_get_synth_probs(spec.get()).empty()) {
|
||||
const uint32_t seed = task.params.sampling.seed == LLAMA_DEFAULT_SEED
|
||||
? std::random_device{}()
|
||||
: task.params.sampling.seed;
|
||||
slot.spec_synth_rng.seed(seed);
|
||||
}
|
||||
} else {
|
||||
slot.smpl.reset();
|
||||
}
|
||||
@@ -3802,7 +3884,12 @@ private:
|
||||
common_sampler_ptr smpl_save(common_sampler_clone(slot.smpl.get()));
|
||||
|
||||
GGML_ASSERT(slot.spec_i_batch.size() == n_draft + 1);
|
||||
auto accepted = common_sampler_sample_and_accept_n(slot.smpl.get(), slot.ctx_tgt, slot.spec_i_batch, slot.spec_draft);
|
||||
const auto & synth_probs = common_speculative_get_synth_probs(spec.get());
|
||||
auto accepted = synth_probs.empty()
|
||||
? common_sampler_sample_and_accept_n(slot.smpl.get(), slot.ctx_tgt, slot.spec_i_batch, slot.spec_draft)
|
||||
: server_sample_and_accept_synth(
|
||||
slot.smpl.get(), slot.ctx_tgt, slot.spec_i_batch, slot.spec_draft,
|
||||
synth_probs, slot.spec_synth_rng, slot.spec_is_replay);
|
||||
slot.spec_i_batch.clear();
|
||||
|
||||
GGML_ASSERT(accepted.size() >= 1);
|
||||
@@ -3868,7 +3955,7 @@ private:
|
||||
|
||||
auto & n_accepted_per_pos = slot.n_accepted_per_pos;
|
||||
if (n_accepted_per_pos.empty()) {
|
||||
n_accepted_per_pos.resize(common_speculative_n_max(¶ms_base.speculative), 0);
|
||||
n_accepted_per_pos.resize(common_speculative_n_max(spec.get()), 0);
|
||||
}
|
||||
for (size_t i = 0; i < n_accepted && i < n_accepted_per_pos.size(); ++i) {
|
||||
n_accepted_per_pos[i]++;
|
||||
@@ -3909,8 +3996,15 @@ private:
|
||||
});
|
||||
}
|
||||
|
||||
int get_slot_n_ctx() {
|
||||
return slots.back().n_ctx;
|
||||
// context size of a single slot, capped by --kv-unified-per-slot and by the training context of the model
|
||||
int n_ctx_slot() const {
|
||||
int res = llama_n_ctx_seq(ctx_tgt);
|
||||
|
||||
if (params_base.kv_unified_per_slot > 0) {
|
||||
res = std::min(res, params_base.kv_unified_per_slot);
|
||||
}
|
||||
|
||||
return std::min(res, llama_model_n_ctx_train(model_tgt));
|
||||
}
|
||||
|
||||
server_response_reader get_response_reader() {
|
||||
@@ -4076,7 +4170,7 @@ server_context_meta server_context::get_meta() const {
|
||||
/* has_inp_audio */ impl->chat_params.allow_audio,
|
||||
/* has_inp_video */ impl->chat_params.allow_video,
|
||||
/* json_ui_settings */ impl->json_ui_settings,
|
||||
/* slot_n_ctx */ impl->get_slot_n_ctx(),
|
||||
/* slot_n_ctx */ impl->n_ctx_slot(),
|
||||
/* pooling_type */ llama_pooling_type(impl->ctx_tgt),
|
||||
|
||||
/* chat_params */ impl->chat_params,
|
||||
|
||||
@@ -157,6 +157,18 @@ int llama_server(common_params & params, int argc, char ** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
// size the KV pool from --kv-unified-per-slot, unless the user pinned it with -c
|
||||
// or with -c 0 for max context
|
||||
const bool ctx_pool_auto_sized = params.kv_unified_per_slot > 0 &&
|
||||
params.n_ctx == 0 &&
|
||||
(uint32_t) params.fit_params_min_ctx != UINT32_MAX;
|
||||
|
||||
if (ctx_pool_auto_sized) {
|
||||
params.n_ctx = params.n_parallel * params.kv_unified_per_slot;
|
||||
SRV_INF("--kv-unified-per-slot: sizing KV pool to n_parallel * kv_unified_per_slot = %d * %d = %d\n", params.n_parallel,
|
||||
params.kv_unified_per_slot, params.n_ctx);
|
||||
}
|
||||
|
||||
// for consistency between server router mode and single-model mode, we set the same model name as alias
|
||||
auto model_name = params.model.get_name();
|
||||
if (params.model_alias.empty() && !model_name.empty()) {
|
||||
|
||||
@@ -52,6 +52,18 @@ def test_with_and_without_draft():
|
||||
|
||||
assert tokens_no_draft == tokens_draft
|
||||
|
||||
server.stop()
|
||||
create_server()
|
||||
assert server.spec_draft_n_max is not None
|
||||
server.spec_synth_rates = [0.0] * server.spec_draft_n_max
|
||||
server.start()
|
||||
res = server.make_request("POST", "/completion", data=request)
|
||||
|
||||
assert res.status_code == 200
|
||||
assert res.body["timings"]["draft_n"] > 0
|
||||
assert res.body["timings"]["draft_n_accepted"] == 0
|
||||
assert res.body["tokens"] == tokens_no_draft
|
||||
|
||||
|
||||
def test_different_draft_min_draft_max():
|
||||
global server
|
||||
@@ -80,6 +92,66 @@ def test_different_draft_min_draft_max():
|
||||
last_content = res.body["content"]
|
||||
|
||||
|
||||
def test_synth_is_deterministic():
|
||||
global server
|
||||
assert server.spec_draft_n_max is not None
|
||||
server.spec_synth_rates = [0.75 ** (i + 1) for i in range(server.spec_draft_n_max)]
|
||||
server.start()
|
||||
|
||||
request = {
|
||||
"prompt": "I believe the meaning of life is",
|
||||
"temperature": 0.2,
|
||||
"top_k": 5,
|
||||
"seed": 4242,
|
||||
"n_predict": 32,
|
||||
}
|
||||
responses = [server.make_request("POST", "/completion", data=request) for _ in range(2)]
|
||||
|
||||
for res in responses:
|
||||
assert res.status_code == 200
|
||||
assert res.body["timings"]["draft_n"] > 0
|
||||
assert responses[0].body["timings"]["draft_n"] == responses[1].body["timings"]["draft_n"]
|
||||
assert responses[0].body["timings"]["draft_n_accepted"] == responses[1].body["timings"]["draft_n_accepted"]
|
||||
|
||||
|
||||
def test_synth_ignores_target_tokens():
|
||||
global server
|
||||
assert server.spec_draft_n_max is not None
|
||||
server.spec_synth_rates = [1.0] * server.spec_draft_n_max
|
||||
server.start()
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": "I believe the meaning of life is",
|
||||
"temperature": 0.0,
|
||||
"seed": 4242,
|
||||
"n_predict": 32,
|
||||
})
|
||||
|
||||
assert res.status_code == 200
|
||||
assert res.body["timings"]["draft_n"] > 0
|
||||
assert res.body["timings"]["draft_n_accepted"] == res.body["timings"]["draft_n"]
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": "I believe the meaning of life is",
|
||||
"temperature": 0.0,
|
||||
"seed": 4242,
|
||||
"n_predict": 6,
|
||||
"grammar": 'root ::= "a"{5,5}',
|
||||
})
|
||||
assert res.status_code == 200, res.body
|
||||
|
||||
res = server.make_request("POST", "/completion", data={
|
||||
"prompt": "Respond with only: OK",
|
||||
"temperature": 0.0,
|
||||
"seed": 4242,
|
||||
"n_predict": 64,
|
||||
"ignore_eos": True,
|
||||
})
|
||||
assert res.status_code == 200, res.body
|
||||
assert res.body["tokens_predicted"] == 64
|
||||
assert res.body["stop_type"] == "limit"
|
||||
|
||||
|
||||
def test_slot_ctx_not_exceeded():
|
||||
global server
|
||||
server.n_ctx = 256
|
||||
|
||||
@@ -99,6 +99,8 @@ class ServerProcess:
|
||||
spec_type: str | None = None
|
||||
spec_draft_n_min: int | None = None
|
||||
spec_draft_n_max: int | None = None
|
||||
spec_synth_len: float | None = None
|
||||
spec_synth_rates: List[float] | None = None
|
||||
no_ui: bool | None = None
|
||||
jinja: bool | None = None
|
||||
reasoning_format: Literal['deepseek', 'none', 'nothink'] | None = None
|
||||
@@ -245,6 +247,11 @@ class ServerProcess:
|
||||
server_args.extend(["--spec-draft-n-max", self.spec_draft_n_max])
|
||||
if self.spec_draft_n_min:
|
||||
server_args.extend(["--spec-draft-n-min", self.spec_draft_n_min])
|
||||
if self.spec_synth_len is not None:
|
||||
server_args.extend(["--spec-synth-len", self.spec_synth_len])
|
||||
if self.spec_synth_rates is not None:
|
||||
rates = ",".join(str(rate) for rate in self.spec_synth_rates)
|
||||
server_args.extend(["--spec-synth-rates", rates])
|
||||
if self.no_ui:
|
||||
server_args.append("--no-ui")
|
||||
if self.no_models_autoload:
|
||||
|
||||
Reference in New Issue
Block a user