@@ -948,6 +948,188 @@ static void llama_grammar_advance_stack(
948948 }
949949}
950950
951+ // Left-factor grammar rules to reduce redundant parallel stacks.
952+ //
953+ // When a rule has many alternates (e.g. tool-choice ::= tc0 | tc1 | ... | tc119)
954+ // and the referenced rules share a common element-content prefix, extract that
955+ // prefix so the parser processes it once instead of N times.
956+ //
957+ // Handles two cases:
958+ // 1. Inline common prefix: rule = "ab1" | "ab2" → rule = "ab" rule', rule' = "1" | "2"
959+ // 2. Cross-rule prefix: rule = A | B | C where A,B,C start with same elements
960+ // → rule = common_prefix rule', rule' = A_suffix | B_suffix | C_suffix
961+ static void llama_grammar_left_factor (llama_grammar_rules & rules) {
962+ auto elem_eq = [](const llama_grammar_element & a, const llama_grammar_element & b) {
963+ return a.type == b.type && a.value == b.value ;
964+ };
965+
966+ struct alt_info {
967+ size_t rule_idx;
968+ size_t start;
969+ size_t end;
970+ };
971+
972+ bool changed = true ;
973+ while (changed) {
974+ changed = false ;
975+ const size_t n_rules = rules.size ();
976+
977+ for (size_t ri = 0 ; ri < n_rules; ri++) {
978+ const auto & rule = rules[ri];
979+
980+ // Split rule into alternates
981+ std::vector<std::pair<size_t , size_t >> alt_ranges;
982+ size_t alt_start = 0 ;
983+ for (size_t j = 0 ; j < rule.size (); j++) {
984+ if (rule[j].type == LLAMA_GRETYPE_ALT || rule[j].type == LLAMA_GRETYPE_END ) {
985+ alt_ranges.push_back ({alt_start, j});
986+ alt_start = j + 1 ;
987+ }
988+ }
989+
990+ if (alt_ranges.size () < 4 ) {
991+ continue ;
992+ }
993+
994+ // Resolve each alternate: if it's a single RULE_REF, follow it to get the
995+ // actual element sequence from the referenced rule.
996+ std::vector<alt_info> resolved;
997+ resolved.reserve (alt_ranges.size ());
998+ bool all_single_refs = true ;
999+
1000+ for (const auto & [astart, aend] : alt_ranges) {
1001+ size_t len = aend - astart;
1002+ if (len == 1 && rule[astart].type == LLAMA_GRETYPE_RULE_REF ) {
1003+ size_t ref_id = static_cast <size_t >(rule[astart].value );
1004+ if (ref_id < rules.size ()) {
1005+ size_t ref_end = 0 ;
1006+ for (size_t k = 0 ; k < rules[ref_id].size (); k++) {
1007+ if (rules[ref_id][k].type == LLAMA_GRETYPE_ALT ||
1008+ rules[ref_id][k].type == LLAMA_GRETYPE_END ) {
1009+ ref_end = k;
1010+ break ;
1011+ }
1012+ }
1013+ bool single_alt = (rules[ref_id][ref_end].type == LLAMA_GRETYPE_END );
1014+ if (single_alt) {
1015+ resolved.push_back ({ref_id, 0 , ref_end});
1016+ continue ;
1017+ }
1018+ }
1019+ }
1020+ all_single_refs = false ;
1021+ resolved.push_back ({ri, astart, aend});
1022+ }
1023+
1024+ if (!all_single_refs) {
1025+ // Inline-only factoring
1026+ size_t prefix_len = 0 ;
1027+ bool prefix_match = true ;
1028+ while (prefix_match) {
1029+ const auto & first = alt_ranges[0 ];
1030+ if (first.first + prefix_len >= first.second ) {
1031+ break ;
1032+ }
1033+ const auto & ref_elem = rule[first.first + prefix_len];
1034+ for (size_t ai = 1 ; ai < alt_ranges.size (); ai++) {
1035+ const auto & alt = alt_ranges[ai];
1036+ if (alt.first + prefix_len >= alt.second ||
1037+ !elem_eq (rule[alt.first + prefix_len], ref_elem)) {
1038+ prefix_match = false ;
1039+ break ;
1040+ }
1041+ }
1042+ if (prefix_match) {
1043+ prefix_len++;
1044+ }
1045+ }
1046+
1047+ if (prefix_len == 0 ) {
1048+ continue ;
1049+ }
1050+
1051+ uint32_t suffix_rule_id = static_cast <uint32_t >(rules.size ());
1052+ llama_grammar_rule suffix_rule;
1053+ for (size_t ai = 0 ; ai < alt_ranges.size (); ai++) {
1054+ if (ai > 0 ) {
1055+ suffix_rule.push_back ({LLAMA_GRETYPE_ALT , 0 });
1056+ }
1057+ const auto & alt = alt_ranges[ai];
1058+ for (size_t j = alt.first + prefix_len; j < alt.second ; j++) {
1059+ suffix_rule.push_back (rule[j]);
1060+ }
1061+ }
1062+ suffix_rule.push_back ({LLAMA_GRETYPE_END , 0 });
1063+
1064+ llama_grammar_rule new_rule;
1065+ for (size_t j = 0 ; j < prefix_len; j++) {
1066+ new_rule.push_back (rule[alt_ranges[0 ].first + j]);
1067+ }
1068+ new_rule.push_back ({LLAMA_GRETYPE_RULE_REF , suffix_rule_id});
1069+ new_rule.push_back ({LLAMA_GRETYPE_END , 0 });
1070+
1071+ rules.push_back (std::move (suffix_rule));
1072+ rules[ri] = std::move (new_rule);
1073+ changed = true ;
1074+ continue ;
1075+ }
1076+
1077+ // Cross-rule factoring: all alternates are single RULE_REFs to single-alternate rules.
1078+ size_t prefix_len = 0 ;
1079+ bool prefix_match = true ;
1080+ while (prefix_match) {
1081+ const auto & first = resolved[0 ];
1082+ if (first.start + prefix_len >= first.end ) {
1083+ break ;
1084+ }
1085+ const auto & ref_elem = rules[first.rule_idx ][first.start + prefix_len];
1086+
1087+ for (size_t ai = 1 ; ai < resolved.size (); ai++) {
1088+ const auto & alt = resolved[ai];
1089+ if (alt.start + prefix_len >= alt.end ||
1090+ !elem_eq (rules[alt.rule_idx ][alt.start + prefix_len], ref_elem)) {
1091+ prefix_match = false ;
1092+ break ;
1093+ }
1094+ }
1095+ if (prefix_match) {
1096+ prefix_len++;
1097+ }
1098+ }
1099+
1100+ if (prefix_len == 0 ) {
1101+ continue ;
1102+ }
1103+
1104+ uint32_t suffix_choice_id = static_cast <uint32_t >(rules.size ());
1105+ llama_grammar_rule suffix_choice;
1106+
1107+ for (size_t ai = 0 ; ai < resolved.size (); ai++) {
1108+ if (ai > 0 ) {
1109+ suffix_choice.push_back ({LLAMA_GRETYPE_ALT , 0 });
1110+ }
1111+ const auto & alt = resolved[ai];
1112+ for (size_t j = alt.start + prefix_len; j < alt.end ; j++) {
1113+ suffix_choice.push_back (rules[alt.rule_idx ][j]);
1114+ }
1115+ }
1116+ suffix_choice.push_back ({LLAMA_GRETYPE_END , 0 });
1117+
1118+ llama_grammar_rule new_rule;
1119+ const auto & first = resolved[0 ];
1120+ for (size_t j = 0 ; j < prefix_len; j++) {
1121+ new_rule.push_back (rules[first.rule_idx ][first.start + j]);
1122+ }
1123+ new_rule.push_back ({LLAMA_GRETYPE_RULE_REF , suffix_choice_id});
1124+ new_rule.push_back ({LLAMA_GRETYPE_END , 0 });
1125+
1126+ rules.push_back (std::move (suffix_choice));
1127+ rules[ri] = std::move (new_rule);
1128+ changed = true ;
1129+ }
1130+ }
1131+ }
1132+
9511133static bool llama_grammar_detect_left_recursion (
9521134 const llama_grammar_rules & rules,
9531135 size_t rule_index,
@@ -1221,12 +1403,16 @@ struct llama_grammar * llama_grammar_init_impl(
12211403 }
12221404 }
12231405
1224- // Check for left recursion
1225- std::vector<bool > rules_visited (n_rules);
1226- std::vector<bool > rules_in_progress (n_rules);
1227- std::vector<bool > rules_may_be_empty (n_rules);
1406+ // Left-factor rules to reduce redundant parallel stacks
1407+ llama_grammar_left_factor (vec_rules);
1408+
1409+ // Check for left recursion (use post-factoring rule count)
1410+ const size_t n_rules_total = vec_rules.size ();
1411+ std::vector<bool > rules_visited (n_rules_total);
1412+ std::vector<bool > rules_in_progress (n_rules_total);
1413+ std::vector<bool > rules_may_be_empty (n_rules_total);
12281414 try {
1229- for (size_t i = 0 ; i < n_rules ; i++) {
1415+ for (size_t i = 0 ; i < n_rules_total ; i++) {
12301416 if (rules_visited[i]) {
12311417 continue ;
12321418 }
@@ -1319,12 +1505,16 @@ struct llama_grammar * llama_grammar_init_impl(
13191505 vec_rules[i].push_back ({LLAMA_GRETYPE_END , 0 });
13201506 }
13211507
1322- // Check for left recursion
1323- std::vector<bool > rules_visited (n_rules);
1324- std::vector<bool > rules_in_progress (n_rules);
1325- std::vector<bool > rules_may_be_empty (n_rules);
1508+ // Left-factor rules to reduce redundant parallel stacks
1509+ llama_grammar_left_factor (vec_rules);
1510+
1511+ // Check for left recursion (use post-factoring rule count)
1512+ const size_t n_rules_total = vec_rules.size ();
1513+ std::vector<bool > rules_visited (n_rules_total);
1514+ std::vector<bool > rules_in_progress (n_rules_total);
1515+ std::vector<bool > rules_may_be_empty (n_rules_total);
13261516 try {
1327- for (size_t i = 0 ; i < n_rules ; i++) {
1517+ for (size_t i = 0 ; i < n_rules_total ; i++) {
13281518 if (rules_visited[i]) {
13291519 continue ;
13301520 }
@@ -1412,14 +1602,15 @@ struct llama_grammar * llama_grammar_clone_impl(const struct llama_grammar & gra
14121602 grammar.trigger_patterns ,
14131603 };
14141604
1415- // redirect elements in stacks to point to new rules
1416- for (size_t is = 0 ; is < result->stacks .size (); is++) {
1417- for (size_t ie = 0 ; ie < result->stacks [is].size (); ie++) {
1418- for (size_t ir0 = 0 ; ir0 < grammar.rules .size (); ir0++) {
1419- for (size_t ir1 = 0 ; ir1 < grammar.rules [ir0].size (); ir1++) {
1420- if (grammar.stacks [is][ie] == &grammar.rules [ir0][ir1]) {
1421- result->stacks [is][ie] = &result->rules [ir0][ir1];
1422- }
1605+ // redirect elements in stacks to point to new rules using per-rule pointer offsets
1606+ for (auto & stack : result->stacks ) {
1607+ for (auto & elem : stack) {
1608+ for (size_t ir = 0 ; ir < grammar.rules .size (); ir++) {
1609+ const auto & old_rule = grammar.rules [ir];
1610+ if (!old_rule.empty () && elem >= old_rule.data () && elem < old_rule.data () + old_rule.size ()) {
1611+ size_t offset = static_cast <size_t >(elem - old_rule.data ());
1612+ elem = result->rules [ir].data () + offset;
1613+ break ;
14231614 }
14241615 }
14251616 }
0 commit comments