// common english strings have the same number of codepoints and bytes. `+ 1` for the terminating 0.
code_points.reserve(src.size() + 1);
uint32_t value = partial_start.value; int n_remain = partial_start.n_remain;
// decode any subsequent utf-8 sequences, which may end in an incomplete one while (*pos != 0) {
uint8_t first_byte = static_cast<uint8_t>(*pos);
uint8_t highbits = first_byte >> 4;
n_remain = lookup[highbits] - 1;
auto handle_repetitions = [&](int min_times, int max_times) {
if (last_sym_start == rule.size()) { throw std::runtime_error(std::string("expecting preceding item to */+/?/{ at ") + pos);
}
// apply transformation to previous symbol (last_sym_start to end) according to // the following rewrite rules: // S{m,n} --> S S S (m times) S'(n-m) // S'(x) ::= S S'(x-1) | // (... n-m definitions of these S' rules ...) // S'(1) ::= S | // S{m,} --> S S S (m times) S' // S' ::= S S' | // S* --> S{0,} // --> S' ::= S S' | // S+ --> S{1,} // --> S S' // S' ::= S S' | // S? --> S{0,1} // --> S' // S' ::= S |
llama_grammar_rule prev_rule(rule.begin() + last_sym_start, rule.end()); if (min_times == 0) {
rule.resize(last_sym_start);
} else { // Repeat the previous elements (min_times - 1) times for (int i = 1; i < min_times; i++) {
rule.insert(rule.end(), prev_rule.begin(), prev_rule.end());
}
}
if (*pos == '\r') {
pos += pos[1] == '\n' ? 2 : 1;
} elseif (*pos == '\n') {
pos++;
} elseif (*pos) { throw std::runtime_error(std::string("expecting newline or end at ") + pos);
} return parse_space(pos, true);
}
bool llama_grammar_parser::parse(constchar * src) {
try { constchar * pos = parse_space(src, true); while (*pos) {
pos = parse_rule(pos);
} // Validate the state to ensure that all rules are defined for (constauto & rule : rules) { if (rule.empty()) { throw std::runtime_error("Undefined rule");
} for (constauto & elem : rule) { if (elem.type == LLAMA_GRETYPE_RULE_REF) { // Ensure that the rule at that location exists if (elem.value >= rules.size() || rules[elem.value].empty()) { // Get the name of the rule that is missing for (constauto & kv : symbol_ids) { if (kv.second == elem.value) { throw std::runtime_error("Undefined rule identifier '" + kv.first + "'");
}
}
}
}
}
}
} catch (const std::exception & err) {
fprintf(stderr, "%s: error parsing grammar: %s\n\n%s\n", __func__, err.what(), src);
rules.clear(); returnfalse;
}
// returns true iff pos points to the end of one of the definitions of a rule staticbool llama_grammar_is_end_of_sequence(const llama_grammar_element * pos) { switch (pos->type) { case LLAMA_GRETYPE_END: return true; // NOLINT case LLAMA_GRETYPE_ALT: return true; // NOLINT default: returnfalse;
}
}
// returns true iff chr satisfies the char range at pos (regular or inverse range) // asserts that pos is pointing to a char range element static std::pair<bool, const llama_grammar_element *> llama_grammar_match_char( const llama_grammar_element * pos, const uint32_t chr) { bool found = false; bool is_positive_char = pos->type == LLAMA_GRETYPE_CHAR || pos->type == LLAMA_GRETYPE_CHAR_ANY;
do { if (pos[1].type == LLAMA_GRETYPE_CHAR_RNG_UPPER) { // inclusive range, e.g. [a-z]
found = found || (pos->value <= chr && chr <= pos[1].value);
pos += 2;
} elseif (pos->type == LLAMA_GRETYPE_CHAR_ANY) { // Any character matches "."
found = true;
pos += 1;
} else { // exact char match, e.g. [a] or "a"
found = found || pos->value == chr;
pos += 1;
}
} while (pos->type == LLAMA_GRETYPE_CHAR_ALT);
// returns true iff some continuation of the given partial UTF-8 sequence could satisfy the char // range at pos (regular or inverse range) // asserts that pos is pointing to a char range element staticbool llama_grammar_match_partial_char( const llama_grammar_element * pos, const llama_partial_utf8 partial_utf8) { bool is_positive_char = pos->type == LLAMA_GRETYPE_CHAR || pos->type == LLAMA_GRETYPE_CHAR_ANY;
GGML_ASSERT(is_positive_char || pos->type == LLAMA_GRETYPE_CHAR_NOT);
uint32_t partial_value = partial_utf8.value; int n_remain = partial_utf8.n_remain;
// invalid sequence or 7-bit char split across 2 bytes (overlong) if (n_remain < 0 || (n_remain == 1 && partial_value < 2)) { returnfalse;
}
// range of possible code points this partial UTF-8 sequence could complete to
uint32_t low = partial_value << (n_remain * 6);
uint32_t high = low | ((1 << (n_remain * 6)) - 1);
do { if (pos[1].type == LLAMA_GRETYPE_CHAR_RNG_UPPER) { // inclusive range, e.g. [a-z] if (pos->value <= high && low <= pos[1].value) { return is_positive_char;
}
pos += 2;
} elseif (pos->type == LLAMA_GRETYPE_CHAR_ANY) { // Any character matches "." return true;
} else { // exact char match, e.g. [a] or "a" if (low <= pos->value && pos->value <= high) { return is_positive_char;
}
pos += 1;
}
} while (pos->type == LLAMA_GRETYPE_CHAR_ALT);
return !is_positive_char;
}
// transforms a grammar pushdown stack into N possible stacks, all ending // at a character range (terminal element) staticvoid llama_grammar_advance_stack( const llama_grammar_rules & rules, const llama_grammar_stack & stack,
llama_grammar_stacks & new_stacks) { if (stack.empty()) { if (std::find(new_stacks.begin(), new_stacks.end(), stack) == new_stacks.end()) {
new_stacks.emplace_back(stack);
} return;
}
const llama_grammar_element * pos = stack.back();
switch (pos->type) { case LLAMA_GRETYPE_RULE_REF: { const size_t rule_id = static_cast<size_t>(pos->value); const llama_grammar_element * subpos = rules[rule_id].data(); do { // init new stack without the top (pos)
llama_grammar_stack new_stack(stack.begin(), stack.end() - 1); if (!llama_grammar_is_end_of_sequence(pos + 1)) { // if this rule ref is followed by another element, add that to stack
new_stack.push_back(pos + 1);
} if (!llama_grammar_is_end_of_sequence(subpos)) { // if alternate is nonempty, add to stack
new_stack.push_back(subpos);
}
llama_grammar_advance_stack(rules, new_stack, new_stacks); while (!llama_grammar_is_end_of_sequence(subpos)) { // scan to end of alternate def
subpos++;
} if (subpos->type == LLAMA_GRETYPE_ALT) { // there's another alternate def of this rule to process
subpos++;
} else { break;
}
} while (true); break;
} case LLAMA_GRETYPE_CHAR: case LLAMA_GRETYPE_CHAR_NOT: case LLAMA_GRETYPE_CHAR_ANY: if (std::find(new_stacks.begin(), new_stacks.end(), stack) == new_stacks.end()) { // only add the stack if it's not a duplicate of one we already have
new_stacks.emplace_back(stack);
} break; default: // end of alternate (LLAMA_GRETYPE_END, LLAMA_GRETYPE_ALT) or middle of char range // (LLAMA_GRETYPE_CHAR_ALT, LLAMA_GRETYPE_CHAR_RNG_UPPER); stack should never be left on // those
GGML_ABORT("fatal error");
}
}
// First check if the rule might produce the empty string. This could be done combined with the second // step but it's more readable as two steps. bool at_rule_start = true; for (size_t i = 0; i < rule.size(); i++) { if (llama_grammar_is_end_of_sequence(&rule[i])) { if (at_rule_start) {
(*rules_may_be_empty)[rule_index] = true; break;
}
at_rule_start = true;
} else {
at_rule_start = false;
}
}
// Second, recurse into leftmost nonterminals (or next-leftmost as long as the previous nonterminal may // be empty) bool recurse_into_nonterminal = true; for (size_t i = 0; i < rule.size(); i++) { if (rule[i].type == LLAMA_GRETYPE_RULE_REF && recurse_into_nonterminal) { if (llama_grammar_detect_left_recursion(rules, (size_t)rule[i].value, rules_visited, rules_in_progress, rules_may_be_empty)) { return true;
} if (!((*rules_may_be_empty)[(size_t)rule[i].value])) {
recurse_into_nonterminal = false;
}
} elseif (llama_grammar_is_end_of_sequence(&rule[i])) {
recurse_into_nonterminal = true;
} else {
recurse_into_nonterminal = false;
}
}
for (constauto & stack : grammar->stacks) { if (stack.empty()) { continue;
}
auto match = llama_grammar_match_char(stack.back(), chr); if (match.first) { const llama_grammar_element * pos = match.second;
// update top of stack to next element, if any
llama_grammar_stack new_stack(stack.begin(), stack.end() - 1); if (!llama_grammar_is_end_of_sequence(pos)) {
new_stack.push_back(pos);
}
llama_grammar_advance_stack(grammar->rules, new_stack, stacks_new);
}
}
for (constauto & tok : candidates) { if (*tok.code_points == 0) { // reached end of full codepoints in token, reject iff it ended in a partial sequence // that cannot satisfy this position in grammar if (tok.partial_utf8.n_remain != 0 &&
!llama_grammar_match_partial_char(stack_pos, tok.partial_utf8)) {
rejects.push_back(tok);
}
} elseif (llama_grammar_match_char(stack_pos, *tok.code_points).first) {
next_candidates.push_back({ tok.index, tok.code_points + 1, tok.partial_utf8 });
} else {
rejects.push_back(tok);
}
}
// update top of stack to next element, if any
llama_grammar_stack stack_after(stack.begin(), stack.end() - 1); if (!llama_grammar_is_end_of_sequence(stack_pos_after)) {
stack_after.push_back(stack_pos_after);
}
llama_grammar_stacks next_stacks;
llama_grammar_advance_stack(rules, stack_after, next_stacks);
auto next_rejects = llama_grammar_reject_candidates(rules, next_stacks, next_candidates); for (constauto & tok : next_rejects) {
rejects.push_back({ tok.index, tok.code_points - 1, tok.partial_utf8 });
}
// copy rule definitions into vectors
llama_grammar_rules vec_rules(n_rules); for (size_t i = 0; i < n_rules; i++) { for (pos = rules[i]; pos->type != LLAMA_GRETYPE_END; pos++) {
vec_rules[i].push_back(*pos);
}
vec_rules[i].push_back({LLAMA_GRETYPE_END, 0});
}
// Check for left recursion
std::vector<bool> rules_visited(n_rules);
std::vector<bool> rules_in_progress(n_rules);
std::vector<bool> rules_may_be_empty(n_rules); for (size_t i = 0; i < n_rules; i++) { if (rules_visited[i]) { continue;
} if (llama_grammar_detect_left_recursion(vec_rules, i, &rules_visited, &rules_in_progress, &rules_may_be_empty)) {
LLAMA_LOG_ERROR("unsupported grammar, left recursion detected for nonterminal at index %zu", i); return nullptr;
}
}
// loop over alternates of start rule to build initial stacks
llama_grammar_stacks stacks;
pos = vec_rules[start_rule_index].data(); do {
llama_grammar_stack stack; if (!llama_grammar_is_end_of_sequence(pos)) { // if alternate is nonempty, add to stack
stack.push_back(pos);
}
llama_grammar_advance_stack(vec_rules, stack, stacks); while (!llama_grammar_is_end_of_sequence(pos)) { // scan to end of alternate def
pos++;
} if (pos->type == LLAMA_GRETYPE_ALT) { // there's another alternate def of this rule to process
pos++;
} else { break;
}
} while (true);
// Important: vec_rules has to be moved here, not copied, because stacks contains // pointers to elements of vec_rules. If vec_rules were copied into llama_grammar // then the pointers would be invalidated when the local vec_rules goes out of scope. returnnew llama_grammar {
vocab,
std::move(vec_rules),
std::move(stacks), /* .partial_utf8 = */ {}, /* .lazy =*/ false, /* .awaiting_trigger = */ false, /* .trigger_buffer = */ "", /* .trigger_tokens = */ {}, /* .trigger_patterns = */ {},
};
}
// if there is a grammar, parse it // rules will be empty (default) if there are parse errors if (!parser.parse(grammar_str) || parser.rules.empty()) {
fprintf(stderr, "%s: failed to parse grammar\n", __func__); return nullptr;
}
// Ensure that there is a "root" node. if (parser.symbol_ids.find("root") == parser.symbol_ids.end()) {
fprintf(stderr, "%s: grammar does not contain a 'root' symbol\n", __func__); return nullptr;
}
// copy rule definitions into vectors
llama_grammar_rules vec_rules(n_rules); for (size_t i = 0; i < n_rules; i++) { for (pos = grammar_rules[i]; pos->type != LLAMA_GRETYPE_END; pos++) {
vec_rules[i].push_back(*pos);
}
vec_rules[i].push_back({LLAMA_GRETYPE_END, 0});
}
// Check for left recursion
std::vector<bool> rules_visited(n_rules);
std::vector<bool> rules_in_progress(n_rules);
std::vector<bool> rules_may_be_empty(n_rules); for (size_t i = 0; i < n_rules; i++) { if (rules_visited[i]) { continue;
} if (llama_grammar_detect_left_recursion(vec_rules, i, &rules_visited, &rules_in_progress, &rules_may_be_empty)) {
LLAMA_LOG_ERROR("unsupported grammar, left recursion detected for nonterminal at index %zu", i); return nullptr;
}
}
// loop over alternates of start rule to build initial stacks
llama_grammar_stacks stacks;
pos = vec_rules[start_rule_index].data(); do {
llama_grammar_stack stack; if (!llama_grammar_is_end_of_sequence(pos)) { // if alternate is nonempty, add to stack
stack.push_back(pos);
}
llama_grammar_advance_stack(vec_rules, stack, stacks); while (!llama_grammar_is_end_of_sequence(pos)) { // scan to end of alternate def
pos++;
} if (pos->type == LLAMA_GRETYPE_ALT) { // there's another alternate def of this rule to process
pos++;
} else { break;
}
} while (true);
std::vector<llama_token> vec_trigger_tokens;
std::vector<llama_grammar_trigger_pattern> vec_trigger_patterns; for (size_t i = 0; i < num_trigger_tokens; i++) {
GGML_ASSERT(trigger_tokens != nullptr);
vec_trigger_tokens.push_back(trigger_tokens[i]);
} for (size_t i = 0; i < num_trigger_patterns; i++) {
GGML_ASSERT(trigger_patterns != nullptr); auto & trigger = vec_trigger_patterns.emplace_back();
trigger.pattern = trigger_patterns[i];
trigger.regex = std::regex(trigger.pattern);
}
// Important: vec_rules has to be moved here, not copied, because stacks contains // pointers to elements of vec_rules. If vec_rules were copied into llama_grammar // then the pointers would be invalidated when the local vec_rules goes out of scope. returnnew llama_grammar {
vocab,
std::move(vec_rules),
std::move(stacks), /* .partial_utf8 = */ {}, /* .lazy = */ lazy, /* .awaiting_trigger = */ lazy, /* .trigger_buffer = */ "",
std::move(vec_trigger_tokens),
std::move(vec_trigger_patterns),
};
}
struct llama_grammar * llama_grammar_clone_impl(conststruct llama_grammar & grammar) { auto * result = new llama_grammar {
grammar.vocab,
grammar.rules,
grammar.stacks,
grammar.partial_utf8,
grammar.lazy,
grammar.awaiting_trigger,
grammar.trigger_buffer,
grammar.trigger_tokens,
grammar.trigger_patterns,
};
// redirect elements in stacks to point to new rules for (size_t is = 0; is < result->stacks.size(); is++) { for (size_t ie = 0; ie < result->stacks[is].size(); ie++) { for (size_t ir0 = 0; ir0 < grammar.rules.size(); ir0++) { for (size_t ir1 = 0; ir1 < grammar.rules[ir0].size(); ir1++) { if (grammar.stacks[is][ie] == &grammar.rules[ir0][ir1]) {
result->stacks[is][ie] = &result->rules[ir0][ir1];
}
}
}
}
}
std::smatch match; for (constauto & trigger_pattern : grammar.trigger_patterns) { if (std::regex_match(grammar.trigger_buffer, match, trigger_pattern.regex)) {
grammar.awaiting_trigger = false; // get from the first matched capturing group to the end of the string
size_t start = std::string::npos; for (auto i = 1u; i < match.size(); i++) { if (match.length(i) > 0) {
start = match.position(i); break;
}
} if (start == std::string::npos) {
start = match.position(0);
} auto constrained_str = grammar.trigger_buffer.substr(start); // std::string constrained_str(match[1].first, grammar.trigger_buffer.end());
grammar.trigger_buffer.clear();
llama_grammar_accept_str(grammar, constrained_str);
LLAMA_LOG_DEBUG("Grammar triggered on regex: '%s'\n", constrained_str.c_str()); return;
}
}
LLAMA_LOG_DEBUG("Grammar still awaiting trigger after token %d (`%s`)\n", token, piece.c_str()); return;
}
}
if (grammar.vocab->is_eog(token)) { for (constauto & stack : grammar.stacks) { if (stack.empty()) { return;
}
}
GGML_ABORT("fatal error");
}
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.