voice_audio_multi/src/framework/sampling/hf_sampler.cpp
Fabio c36510c21e
Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
Initial import: audio.cpp with Orange Pi documentation
2026-11-04 19:05:22 +01:00

519 lines
19 KiB
C++

#include "engine/framework/sampling/hf_sampler.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <limits>
#include <stdexcept>
#include <string>
namespace engine::sampling {
namespace {
std::string context_message(std::string_view context, std::string_view message) {
if (context.empty()) {
return std::string(message);
}
std::string out(context);
out += " ";
out += message;
return out;
}
void require_min_tokens(int64_t min_tokens_to_keep) {
if (min_tokens_to_keep <= 0) {
throw std::runtime_error("HF sampler min_tokens_to_keep must be positive");
}
}
class TorchCpuMt19937 {
public:
explicit TorchCpuMt19937(uint64_t seed) {
state_[0] = static_cast<uint32_t>(seed & 0xffffffffU);
for (int index = 1; index < kStateN; ++index) {
state_[index] =
1812433253U * (state_[index - 1] ^ (state_[index - 1] >> 30U)) + static_cast<uint32_t>(index);
}
left_ = 1;
next_ = 0;
}
uint32_t random() {
if (--left_ == 0) {
next_state();
}
uint32_t y = state_[next_++];
y ^= y >> 11U;
y ^= (y << 7U) & 0x9d2c5680U;
y ^= (y << 15U) & 0xefc60000U;
y ^= y >> 18U;
return y;
}
uint64_t random64() {
const uint32_t high = random();
const uint32_t low = random();
return (static_cast<uint64_t>(high) << 32U) | static_cast<uint64_t>(low);
}
void discard(uint64_t count) {
for (uint64_t index = 0; index < count; ++index) {
(void)random();
}
}
void discard_exponential_draws(uint64_t count) {
discard(count * 2ULL);
}
private:
static constexpr int kStateN = 624;
static constexpr int kStateM = 397;
static constexpr uint32_t kMatrixA = 0x9908b0dfU;
static constexpr uint32_t kUpperMask = 0x80000000U;
static constexpr uint32_t kLowerMask = 0x7fffffffU;
static uint32_t mix_bits(uint32_t lhs, uint32_t rhs) {
return (lhs & kUpperMask) | (rhs & kLowerMask);
}
static uint32_t twist(uint32_t lhs, uint32_t rhs) {
return (mix_bits(lhs, rhs) >> 1U) ^ ((rhs & 1U) != 0U ? kMatrixA : 0U);
}
void next_state() {
uint32_t * p = state_.data();
left_ = kStateN;
next_ = 0;
for (int index = kStateN - kStateM + 1; --index != 0; ++p) {
*p = p[kStateM] ^ twist(p[0], p[1]);
}
for (int index = kStateM; --index != 0; ++p) {
*p = p[kStateM - kStateN] ^ twist(p[0], p[1]);
}
*p = p[kStateM - kStateN] ^ twist(p[0], state_[0]);
}
int left_ = 1;
uint32_t next_ = 0;
std::array<uint32_t, kStateN> state_{};
};
float torch_cpu_exponential_float(TorchCpuMt19937 & rng) {
constexpr uint64_t mask = (uint64_t{1} << std::numeric_limits<double>::digits) - 1U;
constexpr double divisor = 1.0 / static_cast<double>(uint64_t{1} << std::numeric_limits<double>::digits);
const double uniform = static_cast<double>(rng.random64() & mask) * divisor;
return static_cast<float>(-std::log1p(-uniform));
}
int32_t sample_torch_cpu_multinomial(
const std::vector<float> & scores,
uint64_t seed,
uint64_t call_index,
std::string_view context) {
TorchCpuMt19937 rng(seed);
rng.discard_exponential_draws(call_index * static_cast<uint64_t>(scores.size()));
float max_score = -std::numeric_limits<float>::infinity();
for (const float score : scores) {
if (std::isfinite(score)) {
max_score = std::max(max_score, score);
}
}
if (!std::isfinite(max_score)) {
throw std::runtime_error(context_message(context, "sampler has no finite logits"));
}
double best_rank = -std::numeric_limits<double>::infinity();
int32_t best_token = -1;
for (size_t token = 0; token < scores.size(); ++token) {
const float exponential = torch_cpu_exponential_float(rng);
if (!std::isfinite(scores[token])) {
continue;
}
const double weight = std::exp(static_cast<double>(scores[token] - max_score));
const double rank = weight / static_cast<double>(exponential);
if (rank > best_rank) {
best_rank = rank;
best_token = static_cast<int32_t>(token);
}
}
if (best_token < 0) {
throw std::runtime_error(context_message(context, "CPU Torch sampler failed to select a token"));
}
return best_token;
}
} // namespace
void HfSamplerScratch::reserve_vocab(size_t vocab_size) {
scores_.reserve(vocab_size);
candidates_.reserve(vocab_size);
weights_.reserve(vocab_size);
seen_.reserve(vocab_size);
}
void HfSamplerScratch::reset_vocab(size_t vocab_size) {
candidates_.clear();
weights_.clear();
probabilities_ready_ = false;
probabilities_scores_data_ = nullptr;
probabilities_scores_size_ = 0;
if (seen_.size() != vocab_size) {
seen_.assign(vocab_size, 0);
seen_generation_ = 1;
} else if (seen_generation_ == 0) {
std::fill(seen_.begin(), seen_.end(), 0);
seen_generation_ = 1;
}
}
int32_t HfLogitsProcessor::argmax(const float * logits, size_t vocab_size, std::string_view context) {
if (logits == nullptr || vocab_size == 0) {
throw std::runtime_error(context_message(context, "sampler cannot select from empty logits"));
}
size_t best = 0;
for (size_t index = 1; index < vocab_size; ++index) {
if (logits[index] > logits[best]) {
best = index;
}
}
return static_cast<int32_t>(best);
}
void HfLogitsProcessor::apply_repetition_penalty(
std::vector<float> & scores,
const std::vector<int32_t> & history,
float penalty,
HfSamplerScratch & scratch) {
apply_repetition_penalty(scores, history.data(), history.size(), penalty, scratch);
}
void HfLogitsProcessor::apply_repetition_penalty(
std::vector<float> & scores,
const int32_t * history,
size_t history_size,
float penalty,
HfSamplerScratch & scratch) {
if (penalty == 1.0F || history_size == 0) {
return;
}
if (!(penalty > 0.0F) || !std::isfinite(penalty)) {
throw std::runtime_error("HF sampler repetition_penalty must be finite and positive");
}
scratch.reset_vocab(scores.size());
const uint32_t generation = scratch.seen_generation_++;
for (size_t index = 0; index < history_size; ++index) {
const int32_t token = history[index];
if (token < 0 || static_cast<size_t>(token) >= scores.size()) {
continue;
}
const size_t token_index = static_cast<size_t>(token);
if (scratch.seen_[token_index] == generation) {
continue;
}
scratch.seen_[token_index] = generation;
float & score = scores[token_index];
score = score < 0.0F ? score * penalty : score / penalty;
}
scratch.probabilities_ready_ = false;
scratch.probabilities_scores_data_ = nullptr;
scratch.probabilities_scores_size_ = 0;
}
void HfLogitsProcessor::apply_top_k(
std::vector<float> & scores,
int64_t top_k,
int64_t min_tokens_to_keep,
HfSamplerScratch & scratch) {
require_min_tokens(min_tokens_to_keep);
if (top_k <= 0 || scores.empty()) {
return;
}
const int64_t keep = std::min<int64_t>(
static_cast<int64_t>(scores.size()),
std::max(top_k, min_tokens_to_keep));
if (keep >= static_cast<int64_t>(scores.size())) {
return;
}
auto & order = scratch.candidates_;
order.resize(scores.size());
for (size_t index = 0; index < scores.size(); ++index) {
order[index] = static_cast<int32_t>(index);
}
auto kth = order.begin() + static_cast<std::ptrdiff_t>(keep - 1);
std::nth_element(order.begin(), kth, order.end(), [&](int32_t lhs, int32_t rhs) {
return scores[static_cast<size_t>(lhs)] > scores[static_cast<size_t>(rhs)];
});
const float threshold = scores[static_cast<size_t>(*kth)];
for (float & score : scores) {
if (score < threshold) {
score = -std::numeric_limits<float>::infinity();
}
}
scratch.probabilities_ready_ = false;
scratch.probabilities_scores_data_ = nullptr;
scratch.probabilities_scores_size_ = 0;
}
void HfLogitsProcessor::apply_top_p(
std::vector<float> & scores,
float top_p,
int64_t min_tokens_to_keep,
HfSamplerScratch & scratch) {
require_min_tokens(min_tokens_to_keep);
if (!(top_p < 1.0F)) {
return;
}
if (top_p < 0.0F || !std::isfinite(top_p)) {
throw std::runtime_error("HF sampler top_p must be finite and in [0, 1]");
}
if (scores.empty()) {
return;
}
auto & sorted = scratch.candidates_;
sorted.clear();
sorted.reserve(scores.size());
for (size_t index = 0; index < scores.size(); ++index) {
if (std::isfinite(scores[index])) {
sorted.push_back(static_cast<int32_t>(index));
}
}
if (sorted.empty()) {
return;
}
std::sort(sorted.begin(), sorted.end(), [&](int32_t lhs, int32_t rhs) {
const float lhs_score = scores[static_cast<size_t>(lhs)];
const float rhs_score = scores[static_cast<size_t>(rhs)];
if (lhs_score == rhs_score) {
return lhs < rhs;
}
return lhs_score < rhs_score;
});
const float max_score = scores[static_cast<size_t>(sorted.back())];
auto & weights = scratch.weights_;
weights.resize(sorted.size());
double total = 0.0;
for (size_t index = 0; index < sorted.size(); ++index) {
const int32_t token = sorted[index];
weights[index] = std::exp(static_cast<double>(scores[static_cast<size_t>(token)] - max_score));
total += weights[index];
}
if (!(total > 0.0) || !std::isfinite(total)) {
throw std::runtime_error("HF sampler top-p probability mass is invalid");
}
double cumulative = 0.0;
size_t kept_count = 0;
const double remove_mass = 1.0 - static_cast<double>(top_p);
const size_t min_keep = std::min<size_t>(static_cast<size_t>(min_tokens_to_keep), sorted.size());
const size_t protected_from = sorted.size() - min_keep;
for (size_t index = 0; index < sorted.size(); ++index) {
const int32_t token = sorted[index];
cumulative += weights[index] / total;
if (index < protected_from && cumulative <= remove_mass) {
scores[static_cast<size_t>(token)] = -std::numeric_limits<float>::infinity();
} else {
sorted[kept_count] = token;
weights[kept_count] = weights[index];
++kept_count;
}
}
sorted.resize(kept_count);
weights.resize(kept_count);
scratch.probabilities_ready_ = true;
scratch.probabilities_scores_data_ = scores.data();
scratch.probabilities_scores_size_ = scores.size();
}
void HfLogitsProcessor::apply_min_p(
std::vector<float> & scores,
float min_p,
int64_t min_tokens_to_keep,
HfSamplerScratch & scratch) {
require_min_tokens(min_tokens_to_keep);
if (min_p < 0.0F || min_p > 1.0F || !std::isfinite(min_p)) {
throw std::runtime_error("HF sampler min_p must be finite and in [0, 1]");
}
if (min_p == 0.0F) {
return;
}
if (scores.empty()) {
return;
}
auto & order = scratch.candidates_;
order.clear();
order.reserve(scores.size());
for (size_t index = 0; index < scores.size(); ++index) {
if (std::isfinite(scores[index])) {
order.push_back(static_cast<int32_t>(index));
}
}
if (order.empty()) {
return;
}
const size_t min_keep = std::min<size_t>(
static_cast<size_t>(min_tokens_to_keep), order.size());
std::partial_sort(
order.begin(),
order.begin() + static_cast<std::ptrdiff_t>(min_keep),
order.end(),
[&](int32_t lhs, int32_t rhs) {
const float lhs_score = scores[static_cast<size_t>(lhs)];
const float rhs_score = scores[static_cast<size_t>(rhs)];
return lhs_score == rhs_score ? lhs < rhs : lhs_score > rhs_score;
});
const float max_score = scores[static_cast<size_t>(order.front())];
const float threshold = max_score + std::log(min_p);
const float protected_threshold =
scores[static_cast<size_t>(order[min_keep - 1])];
for (float & score : scores) {
if (score < threshold && score < protected_threshold) {
score = -std::numeric_limits<float>::infinity();
}
}
scratch.probabilities_ready_ = false;
scratch.probabilities_scores_data_ = nullptr;
scratch.probabilities_scores_size_ = 0;
}
void HfLogitsProcessor::apply_temperature(std::vector<float> & scores, float temperature) {
if (!(temperature > 0.0F) || !std::isfinite(temperature)) {
throw std::runtime_error("HF sampler temperature must be finite and positive");
}
if (temperature == 1.0F) {
return;
}
for (float & score : scores) {
score /= temperature;
}
}
void HfLogitsProcessor::build_candidates(
const std::vector<float> & scores,
HfSamplerScratch & scratch,
std::string_view context) {
scratch.candidates_.clear();
scratch.weights_.clear();
scratch.candidates_.reserve(scores.size());
float max_score = -std::numeric_limits<float>::infinity();
for (size_t index = 0; index < scores.size(); ++index) {
if (std::isfinite(scores[index])) {
scratch.candidates_.push_back(static_cast<int32_t>(index));
max_score = std::max(max_score, scores[index]);
}
}
if (scratch.candidates_.empty() || !std::isfinite(max_score)) {
throw std::runtime_error(context_message(context, "sampler has no finite logits"));
}
scratch.weights_.reserve(scratch.candidates_.size());
for (const int32_t token : scratch.candidates_) {
const double weight = std::exp(static_cast<double>(scores[static_cast<size_t>(token)] - max_score));
scratch.weights_.push_back(weight);
}
scratch.probabilities_ready_ = true;
scratch.probabilities_scores_data_ = scores.data();
scratch.probabilities_scores_size_ = scores.size();
}
int32_t HfTokenSampler::sample_from_processed_scores(
const std::vector<float> & scores,
HfSamplerScratch & scratch,
std::mt19937 & fallback_rng,
const HfTorchSamplingState * torch_state,
std::string_view context,
bool use_ready_candidates) {
const bool candidates_match_scores =
use_ready_candidates &&
scratch.probabilities_ready_ &&
scratch.probabilities_scores_data_ == scores.data() &&
scratch.probabilities_scores_size_ == scores.size();
if (torch_state != nullptr && torch_state->policy != nullptr && torch_state->policy->cuda_fast_path) {
if (!candidates_match_scores) {
HfLogitsProcessor::build_candidates(scores, scratch, context);
}
const uint64_t population_size = torch_state->population_size == 0
? static_cast<uint64_t>(scores.size())
: torch_state->population_size;
if (torch_state->token_index_offset + scores.size() > population_size) {
throw std::runtime_error(context_message(context, "CUDA sampler population range is invalid"));
}
double best_rank = -std::numeric_limits<double>::infinity();
int32_t best_token = -1;
for (size_t index = 0; index < scratch.candidates_.size(); ++index) {
const int32_t token = scratch.candidates_[index];
const uint64_t population_token =
torch_state->token_index_offset + static_cast<uint64_t>(token);
const float exponential = torch_state->use_offset_blocks
? torch_cuda_tensor_iterator_exponential_element_at_offset(
torch_state->seed,
population_size,
population_token,
torch_state->offset_blocks,
torch_state->policy->multiprocessor_count,
torch_state->policy->max_threads_per_multiprocessor)
: torch_cuda_tensor_iterator_exponential_element(
torch_state->seed,
population_size,
population_token,
torch_state->call_index,
torch_state->policy->multiprocessor_count,
torch_state->policy->max_threads_per_multiprocessor);
const double rank = scratch.weights_[index] / static_cast<double>(exponential);
if (rank > best_rank) {
best_rank = rank;
best_token = token;
}
}
if (best_token < 0) {
throw std::runtime_error(context_message(context, "CUDA sampler failed to select a token"));
}
return best_token;
}
if (torch_state != nullptr) {
return sample_torch_cpu_multinomial(scores, torch_state->seed, torch_state->call_index, context);
}
HfLogitsProcessor::build_candidates(scores, scratch, context);
std::discrete_distribution<size_t> distribution(scratch.weights_.begin(), scratch.weights_.end());
return scratch.candidates_[distribution(fallback_rng)];
}
int32_t HfSampler::sample(
const std::vector<float> & logits,
const std::vector<int32_t> & history,
const HfSamplingOptions & options,
HfSamplerScratch & scratch,
std::mt19937 & fallback_rng,
const HfTorchSamplingState * torch_state,
std::string_view context) const {
if (logits.empty()) {
throw std::runtime_error(context_message(context, "sampler cannot select from empty logits"));
}
const bool needs_repetition_penalty = options.repetition_penalty != 1.0F && !history.empty();
const bool needs_sampling_processors =
options.do_sample &&
(options.temperature != 1.0F || options.top_k > 0 ||
options.top_p < 1.0F || options.min_p > 0.0F);
if (!needs_repetition_penalty && !needs_sampling_processors) {
if (!options.do_sample) {
return HfLogitsProcessor::argmax(logits.data(), logits.size(), context);
}
scratch.probabilities_ready_ = false;
return HfTokenSampler::sample_from_processed_scores(logits, scratch, fallback_rng, torch_state, context);
}
auto & scores = scratch.scores_;
scores.assign(logits.begin(), logits.end());
scratch.probabilities_ready_ = false;
HfLogitsProcessor::apply_repetition_penalty(scores, history, options.repetition_penalty, scratch);
if (!options.do_sample) {
return HfLogitsProcessor::argmax(scores.data(), scores.size(), context);
}
HfLogitsProcessor::apply_temperature(scores, options.temperature);
HfLogitsProcessor::apply_top_k(scores, options.top_k, options.min_tokens_to_keep, scratch);
HfLogitsProcessor::apply_top_p(scores, options.top_p, options.min_tokens_to_keep, scratch);
HfLogitsProcessor::apply_min_p(scores, options.min_p, options.min_tokens_to_keep, scratch);
return HfTokenSampler::sample_from_processed_scores(scores, scratch, fallback_rng, torch_state, context, true);
}
} // namespace engine::sampling