// the ring buffer works similarly to std::deque, but with a fixed capacity template<typename T> struct ring_buffer {
ring_buffer(size_t cap) : capacity(cap), data(cap) {}
T & front() { if (sz == 0) { throw std::runtime_error("ring buffer is empty");
} return data[first];
}
const T & front() const { if (sz == 0) { throw std::runtime_error("ring buffer is empty");
} return data[first];
}
T & back() { if (sz == 0) { throw std::runtime_error("ring buffer is empty");
} return data[pos];
}
const T & back() const { if (sz == 0) { throw std::runtime_error("ring buffer is empty");
} return data[pos];
}
void push_back(const T & value) { if (capacity == 0) { throw std::runtime_error("ring buffer: capacity is zero");
}
if (sz == capacity) { // advance the start when buffer is full
first = (first + 1) % capacity;
} else {
sz++;
}
data[pos] = value;
pos = (pos + 1) % capacity;
}
T pop_front() { if (sz == 0) { throw std::runtime_error("ring buffer is empty");
}
T value = data[first];
first = (first + 1) % capacity;
sz--; return value;
}
//T & operator[](size_t i) { // if (i >= sz) { // throw std::runtime_error("ring buffer: index out of bounds"); // } // return data[(first + i) % capacity]; //}
//const T & at(size_t i) const { // if (i >= sz) { // throw std::runtime_error("ring buffer: index out of bounds"); // } // return data[(first + i) % capacity]; //}
const T & rat(size_t i) const { if (i >= sz) { throw std::runtime_error("ring buffer: index out of bounds");
} return data[(first + sz - i - 1) % capacity];
}
std::vector<T> to_vector() const {
std::vector<T> result;
result.reserve(sz); for (size_t i = 0; i < sz; i++) {
result.push_back(data[(first + i) % capacity]);
} return result;
}
void clear() { // here only reset the status of the buffer
sz = 0;
first = 0;
pos = 0;
}
staticvoid llama_sampler_temp_impl(llama_token_data_array * cur_p, float temp) { if (temp <= 0.0f) { // find the token with the highest logit and set the rest to -inf
size_t max_i = 0; float max_l = cur_p->data[0].logit;
for (size_t i = 1; i < cur_p->size; ++i) { if (cur_p->data[i ].logit > max_l) {
cur_p->data[max_i].logit = -INFINITY;
max_i = i;
max_l = cur_p->data[i].logit;
} else {
cur_p->data[i].logit = -INFINITY;
}
}
return;
}
for (size_t i = 0; i < cur_p->size; ++i) {
cur_p->data[i].logit /= temp;
}
}
for (size_t i = 0; i < cur_p->size; ++i) { float p = expf(cur_p->data[i].logit - max_l);
cur_p->data[i].p = p;
cum_sum += p;
}
for (size_t i = 0; i < cur_p->size; ++i) {
cur_p->data[i].p /= cum_sum;
}
}
staticvoid llama_sampler_top_k_impl(llama_token_data_array * cur_p, int32_t k) { // TODO: move bucket sort to separate function so that top_p/typical/softmax first is equally fast // if (k >= (int32_t)cur_p->size) { // return; // }
if (k <= 0) { return;
}
k = std::min(k, (int) cur_p->size);
// Sort scores in descending order if (!cur_p->sorted) { auto comp = [](const llama_token_data & a, const llama_token_data & b) { return a.logit > b.logit;
}; if (k <= 128) {
std::partial_sort(cur_p->data, cur_p->data + k, cur_p->data + cur_p->size, comp);
} else {
constexpr int nbuckets = 128;
constexpr float bucket_low = -10.0f;
constexpr float bucket_high = 10.0f;
constexpr float bucket_scale = nbuckets/(bucket_high - bucket_low);
constexpr float bucket_inter = -bucket_low * bucket_scale;
if (seed == LLAMA_DEFAULT_SEED) { // use system clock if std::random_device is not a true RNG staticbool is_rd_prng = true;//std::random_device().entropy() == 0; if (is_rd_prng) { return (uint32_t) std::chrono::system_clock::now().time_since_epoch().count();
}
std::random_device rd; return rd();
} return seed;
}
// TODO: do not allocate each time
std::vector<llama_token_data> cur;
cur.reserve(n_vocab); for (llama_token token_id = 0; token_id < n_vocab; token_id++) {
cur.emplace_back(llama_token_data{token_id, logits[token_id], 0.0f});
}
for (size_t i = 0; i < cur_p->size; ++i) {
cum_sum += cur_p->data[i].p;
// Check if the running sum is at least p or if we have kept at least min_keep tokens // we set the last index to i+1 to indicate that the current iterate should be included in the set if (cum_sum >= ctx->p && i + 1 >= ctx->min_keep) {
last_idx = i + 1; break;
}
}
// Resize the output vector to keep only the top-p tokens
cur_p->size = last_idx;
}
// if the cur_p aren't sorted, try the unsorted implementation first if (!cur_p->sorted) {
std::vector<llama_token_data> filtered_tokens;
float max_logit = -FLT_MAX; for (size_t i = 0; i < cur_p->size; ++i) {
max_logit = std::max(max_logit, cur_p->data[i].logit);
} constfloat min_logit = max_logit + logf(ctx->p); // min logit for p_i >= p * p_max
for (size_t i = 0; i < cur_p->size; ++i) { if (cur_p->data[i].logit >= min_logit) {
filtered_tokens.push_back(cur_p->data[i]);
}
}
// if we have enough values the operation was a success if (!filtered_tokens.empty() && filtered_tokens.size() >= ctx->min_keep) {
memcpy(cur_p->data, filtered_tokens.data(), filtered_tokens.size()*sizeof(llama_token_data));
cur_p->size = filtered_tokens.size();
min_p_applied = true;
}
}
// if the cur_p are sorted or the unsorted implementation failed, use this implementation if (!min_p_applied) { // Sort the logits in descending order if (!cur_p->sorted) {
std::sort(cur_p->data, cur_p->data + cur_p->size, [](const llama_token_data & a, const llama_token_data & b) { return a.logit > b.logit;
});
cur_p->sorted = true;
}
constfloat min_logit = cur_p->data[0].logit + logf(ctx->p); // min logit for p_i >= p * p_max
size_t i = 1; // first token always matches
for (; i < cur_p->size; ++i) { if (cur_p->data[i].logit < min_logit && i >= ctx->min_keep) { break; // prob too small
}
}
// Resize the output vector to keep only the matching tokens
cur_p->size = i;
}
}
// Compute the softmax of logits and calculate entropy
llama_sampler_softmax_impl(cur_p);
float entropy = 0.0f; for (size_t i = 0; i < cur_p->size; ++i) {
entropy += -cur_p->data[i].p * logf(cur_p->data[i].p);
}
// Compute the absolute difference between negative log probability and entropy for each candidate
std::vector<float> shifted_scores; for (size_t i = 0; i < cur_p->size; ++i) { float shifted_score = fabsf(-logf(cur_p->data[i].p) - entropy);
shifted_scores.push_back(shifted_score);
}
// Sort tokens based on the shifted_scores and their corresponding indices
std::vector<size_t> indices(cur_p->size);
std::iota(indices.begin(), indices.end(), 0);
std::sort(indices.begin(), indices.end(), [&](size_t a, size_t b) { return shifted_scores[a] < shifted_scores[b];
});
for (size_t i = 0; i < indices.size(); ++i) {
size_t idx = indices[i];
cum_sum += cur_p->data[idx].p;
// Check if the running sum is greater than typical or if we have kept at least min_keep tokens if (cum_sum > ctx->p && (ctx->min_keep == 0 || i >= ctx->min_keep - 1)) {
last_idx = i + 1; break;
}
}
// Resize the output vector to keep only the locally typical tokens
std::vector<llama_token_data> cur_p_new; for (size_t i = 0; i < last_idx; ++i) {
size_t idx = indices[i];
cur_p_new.push_back(cur_p->data[idx]);
}
// Replace the data in cur_p with the cur_p_new data
std::copy(cur_p_new.begin(), cur_p_new.end(), cur_p->data);
cur_p->size = cur_p_new.size();
cur_p->sorted = false;
}
// no need to do anything if there is only one (or zero) candidates if (cur_p->size <= 1) { return;
}
// Calculate maximum possible entropy float max_entropy = -logf(1.0f / cur_p->size);
llama_sampler_softmax_impl(cur_p);
// Calculate entropy of the softmax probabilities float entropy = 0.0f; for (size_t i = 0; i < cur_p->size; ++i) { float prob = cur_p->data[i].p; if (prob > 0.0f) { // Ensure no log(0)
entropy -= prob * logf(prob);
}
}
// Normalize the entropy (max_entropy cannot be 0 here because we checked cur_p->size != 1 above) float normalized_entropy = entropy / max_entropy;
// Map the normalized entropy to the desired temperature range using the power function float dyn_temp = min_temp + (max_temp - min_temp) * powf(normalized_entropy, exponent_val);
#ifdef DEBUG
LLAMA_LOG_INFO("Your text maxtemp value is: %f\n", max_temp);
LLAMA_LOG_INFO("Entropy: %f\n", entropy);
LLAMA_LOG_INFO("Max Possible Entropy: %f\n", max_entropy);
LLAMA_LOG_INFO("Normalized Entropy: %f\n", normalized_entropy);
LLAMA_LOG_INFO("Exponent: %f\n", exponent_val);
LLAMA_LOG_INFO("Dynamic Temperature (dyn_temp): %f\n", dyn_temp); #endif
// Apply the dynamically calculated temperature scaling
llama_sampler_temp_impl(cur_p, dyn_temp);
// Re-compute softmax probabilities after scaling logits with dynamic temperature constdouble max_l_double = cur_p->data[0].logit;
double cum_sum_double = 0.0; for (size_t i = 0; i < cur_p->size; ++i) { double p = exp(cur_p->data[i].logit - max_l_double);
cur_p->data[i].p = p; // Store the scaled probability
cum_sum_double += p;
}
for (size_t i = 0; i < cur_p->size; ++i) {
cur_p->data[i].p /= cum_sum_double; // Re-normalize the probabilities
}
#ifdef DEBUG // Print the updated top 25 probabilities after temperature scaling
LLAMA_LOG_INFO("\nUpdated Top 25 Probabilities After Dynamic Temperature Scaling (in percentages):\n"); for (size_t i = 0; i < 25 && i < cur_p->size; ++i) {
LLAMA_LOG_INFO("Token %zu: %f%%\n", i + 1, cur_p->data[i].p * 100.0f);
} #endif
} else {
llama_sampler_temp_impl(cur_p, ctx->temp);
}
}
// Apply frequency and presence penalties to the cur_p for (size_t i = 0; i < cur_p->size; ++i) { constauto token_iter = ctx->token_count.find(cur_p->data[i].id); if (token_iter == ctx->token_count.end()) { continue;
}
// The academic publication that described this technique actually just only divided, but that would cause tokens with negative logits to become more likely, which is obviously wrong. // This is common fix for this problem, which is to multiply by the penalty instead of dividing. if (cur_p->data[i].logit <= 0) {
cur_p->data[i].logit *= ctx->penalty_repeat;
} else {
cur_p->data[i].logit /= ctx->penalty_repeat;
}
// Ported from Koboldcpp, original PR: https://github.com/LostRuins/koboldcpp/pull/982(Original author: pi6am) staticvoid get_overlapping_token_sequences(const llama_vocab & vocab, const std::string& str, std::unordered_multimap<llama_token, std::vector<llama_token>>& token_sequences, int max_tail_len = -1) { for (llama_token token_id = 0; token_id < (llama_token) vocab.n_tokens(); token_id++) {
std::string word = vocab.detokenize({token_id}, true); if (word.find(str) != std::string::npos) {
token_sequences.emplace(token_id, std::vector<llama_token>());
} else {
size_t word_len = word.size();
size_t str_len = str.size();
size_t pos = -1; while ((pos = word.find(str[0], pos + 1)) != std::string::npos) { bool match = true;
size_t i; for (i = 1; i < str_len && i + pos < word_len; ++i) { if (word[pos + i] != str[i]) {
match = false; break;
}
} if (match) {
std::vector<llama_token> tokenization = vocab.tokenize(str.substr(i), false, false); if (max_tail_len >= 0 && tokenization.size() > (size_t)max_tail_len) {
tokenization.resize(max_tail_len);
}
// Ensure we don't already have a duplicate matching tokenization auto its = token_sequences.equal_range(token_id); bool found = false; for (auto it = its.first; it != its.second; ++it) { if (tokenization == it->second) {
found = true; break;
}
} if (!found) {
token_sequences.emplace(token_id, tokenization);
}
}
}
}
}
}
// Step 1: Look for restart sequences to limit the maximum repetition length. // Work backwards through the context looking for any token that begins a restart sequence. // // The collection `restart_sequences` is a mapping from a "head" token to all "tail" // sequences that together comprise a restart sequence. This allows us to quickly check // whether each token is the head of a complete sequence. Most restart sequences are actually // a single token, and for these the "tail" is an empty vector. // // If the token is a "head", test all restart sequences that begin with this token // (there will often only be one sequence for each token, but if sequences like 'aaaq1' and // 'aaa1' are used as restart strings, both could start with 'aaa' when tokenized). The // longest matching sequence (if any) is used to limit the maximum repetition length. // // Note that in the case case of a short sequence contained in a longer one, this might fail to // find the smallest value for `rep_limit`. For example, if 'amniotic' and 'ni' are both used as // restart sequences, 'ni' will be found first, and since it's shorter it will fail to suppress // 'otic'. This is a minor issue since fully contained restart sequences are likely to be rare. // // This is theoretically worst-case O(N^2) for arbitrary restart sequences, which is why we // have already clamped the maximum tail sequence length when generating `restart_sequences`. // With clamping, this scan is O(N) in the context length.
int rep_limit = last_n_repeat; for (int i = 0; i < last_n_repeat; ++i) {
llama_token token = ctx->last_tokens.rat(i); auto its = ctx->dry_processed_breakers.equal_range(token); if (its.first == ctx->dry_processed_breakers.end()) { continue;
} int longest_match = -1; for (auto it = its.first; it != its.second; ++it) { // Note that (*it) does not contain the head character, so seq_len will be // the restart sequence length minus 1. // In the common case of a single-token restart sequence, (*it) will be empty // and we will trivially match. int seq_len = (int)it->second.size(); if (seq_len > longest_match && seq_len <= (int)i) { bool match = true; for (int offset = 0; offset < seq_len; ++offset) { // The -1 when indexing `last_tokens` is because we already matched the head. if (it->second[offset] != ctx->last_tokens.rat(i - offset - 1)) {
match = false; break;
}
} if (match) {
longest_match = seq_len;
}
}
} if (longest_match >= 0) { // We found a restart sequence starting `i` tokens from the end and continuing for // `longest_match` tokens.
rep_limit = i - longest_match; break;
}
} if (rep_limit < ctx->dry_allowed_length) { return;
}
// Step 2: Iterate in reverse over the last N tokens of the context, using the "Z-algorithm" (in // the reverse direction) to efficiently compute the positions and lengths of suffixes appearing // elsewhere in the context. We limit the suffix length to `rep_limit` to respect restart sequences. // // This algorithm is not currently documented on Wikipedia, but there is a clear description here: // https://ivanyu.me/blog/2014/10/15/z-algorithm/ // // The code below is adapted from the public domain implementation by the same author here: // https://github.com/ivanyu/string-algorithms/blob/master/z_algorithm.py // // Example: // Last N tokens: a b c c b c y a b c // Repeat counts: 0 0 3 1 0 2 0 0 0 0 // ^ // This `3` means that the last three tokens of the context (a b c) also appear here. // // This step is worst case O(N) since the Z-algorithm is linear, despite the appearance of nested // for/while loops. This can be seen by observing that the `lt` and `rt` bounds are set after each // repeated suffix is detected (i.e. after each while loop when n > 0). These bound variables // ensure that the inner while loops only examine each token in the context once as the outer // for loop iterates over the context.
{ constint last = last_n_repeat - 1; int rt = 0, lt = 0;
for (int k = 1; k < last_n_repeat; ++k) { if (k > rt) { // If k is outside the current Z-box, do naive computation. int n = 0; while (n + k < last_n_repeat && ctx->last_tokens.rat(n) == ctx->last_tokens.rat(n+k)) {
++n;
}
ctx->dry_repeat_count[last - k] = std::min(n, rep_limit); if (n > 0) {
lt = k;
rt = k + n - 1;
}
} else { // If k is inside the current Z-box, consider two cases.
int p = k - lt; // Pair index. int right_part_len = rt - k + 1;
if (ctx->dry_repeat_count[last - p] < right_part_len) { int n = std::min(ctx->dry_repeat_count[last - p], rep_limit);
ctx->dry_repeat_count[last - k] = n;
} else { int i = rt + 1; while (i < last_n_repeat && ctx->last_tokens.rat(i) == ctx->last_tokens.rat(i - k)) {
i += 1;
}
int n = std::min(i - k, rep_limit);
ctx->dry_repeat_count[last - k] = n;
lt = k;
rt = i - 1;
}
}
}
}
// Step 3: Iterate over dry_repeat_count and last_tokens, examining the maximum repeat length // that would be generated by emitting each new token that would extend a sequence. // // Following the same example as above: // Last N tokens: a b c c b c y a b c // Repeat counts: 0 0 3 1 0 2 0 0 0 0 // // For each non-zero, look ahead one token. This token, if emitted, would extend the repetition. // c: 3 -> 4 (from `a b c` to `a b c c`) // b: 1 -> 2 (from `c` to `c b`) // y: 2 -> 3 (from `b c` to `b c y`)
for (int i = 0; i < last_n_repeat - 1; ++i) { int repeat_len = ctx->dry_repeat_count[i]; if (repeat_len >= ctx->dry_allowed_length) { // This token ends a repeat, so the next token would continue one. // By convention, the value of `repeat_len` only includes the tokens currently // in the context, not the new token that would be added.
llama_token token = ctx->last_tokens.rat(last_n_repeat - 2 - i); // Track the maximum sequence ending in this token. constauto& it = ctx->dry_max_token_repeat.find(token); if (it == ctx->dry_max_token_repeat.end() || it->second < repeat_len) {
ctx->dry_max_token_repeat[token] = repeat_len;
}
}
}
// Step 4: Apply logit penalties based on the maximum repeat length for relevant tokens.
// Prevent floating point overflow in `pow(penalty_base, exponent)` by clamping to `max_exponent`. // Compute it from `penalty_base` and the approximate log of `std::numeric_limits<float>::max()` constfloat FLOAT_MAX_LOG = 88.7228391f; int max_exponent = 0; if (ctx->dry_base > 1.000001f) {
max_exponent = FLOAT_MAX_LOG / std::log(ctx->dry_base);
}
for (size_t i = 0; i < cur_p->size; ++i) { constauto& af_kvp = ctx->dry_max_token_repeat.find(cur_p->data[i].id); if (af_kvp != ctx->dry_max_token_repeat.end()) { // Check all sequence breakers starting with this token auto range = ctx->dry_processed_breakers.equal_range(cur_p->data[i].id); bool is_single_token_breaker = false;
for (auto it = range.first; it != range.second; ++it) { if (it->second.empty()) {
is_single_token_breaker = true; break;
}
}
// Apply penalty only if it's not a single-token sequence breaker if (!is_single_token_breaker) { int repeat_exp = af_kvp->second - ctx->dry_allowed_length; if (max_exponent > 0 && repeat_exp > max_exponent) {
repeat_exp = max_exponent;
} float penalty = ctx->dry_multiplier * std::pow(ctx->dry_base, repeat_exp);
cur_p->data[i].logit -= penalty;
}
}
}
// dummy vocab is passed because it is only needed for raw sequence breaker processing, which we have already done and will simply be copying auto * result = llama_sampler_init_dry(&dummy_vocab, ctx->total_context_size, ctx->dry_multiplier, ctx->dry_base, ctx->dry_allowed_length, ctx->dry_penalty_last_n, NULL, 0);
// Copy the state, including the processed breakers
{ auto * result_ctx = (llama_sampler_dry *) result->ctx;
result_ctx->dry_processed_breakers = ctx->dry_processed_breakers;
result_ctx->dry_repeat_count = ctx->dry_repeat_count;
result_ctx->dry_max_token_repeat = ctx->dry_max_token_repeat;
result_ctx->last_tokens = ctx->last_tokens;
}
// update the candidates that have not been shuffled in the vocabulary (i.e. idx == id) for (constauto & lb : ctx->logit_bias) { if (lb.token >= 0 && cur_p->size > (size_t) lb.token && cur_p->data[lb.token].id == lb.token) {
cur_p->data[lb.token].logit += lb.bias;
} else {
ctx->to_search.push_back(lb);
}
}
if (ctx->to_search.empty()) { return;
}
// search for the remaining candidates that were not found in the previous step for (size_t i = 0; i < cur_p->size; ++i) { for (constauto & lb : ctx->to_search) { if (cur_p->data[i].id == lb.token) {
cur_p->data[i].logit += lb.bias; break;
}
}
}
}
LOG_DBG_CUR("%s: p_txt_sum = %.2f, p_eog_sum = %.2f, rat = %.2f, n = %zu\n", __func__, p_txt_sum, p_eog_sum, rat, cur_p->size);
if (3*p_eog_sum*cur_p->size > p_txt_sum) {
LOG_DBG_CUR("%s: the ratio p_txt/p_eog = %.2f is too low -> sampling EOG\n", __func__, p_txt_sum/p_eog_sum);
// keep just the EOG tokens constauto size_org = cur_p->size;
cur_p->size = 0;
float p_sum = 0.0f;
for (size_t i = 0; i < size_org; ++i) { if (ctx->vocab->is_eog(cur_p->data[i].id)) {
p_sum += cur_p->data[i].p;
cur_p->data[cur_p->size++] = cur_p->data[i];
}
}
// normalize probs for (size_t i = 0; i < cur_p->size; ++i) {
cur_p->data[i].p /= p_sum;
}
return;
}
size_t n_combined = 0; GGML_UNUSED(n_combined);
// combine tokens with common prefix for (size_t i0 = 0; i0 < cur_p->size; ++i0) { for (size_t i1 = 0; i1 < cur_p->size; ++i1) { if (cur_p->data[i0].logit == -INFINITY) { break;
}
// if no non-EOG tokens are left -> reduce cur_p to single EOT token if (n_non_eog == 0) {
cur_p->size = 1;
cur_p->data[0].id = ctx->vocab->token_eot();
cur_p->data[0].logit = 1.0f;
return;
}
// normalize probs for (size_t i = 0; i < cur_p->size; ++i) {
cur_p->data[i].p /= p_sum;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.