Skip to content

Commit 5254a79

Browse files
authored
common : support manually triggering the reasoning budget end sequence (ggml-org#23949)
1 parent e22b0de commit 5254a79

5 files changed

Lines changed: 108 additions & 1 deletion

File tree

common/reasoning-budget.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,3 +247,24 @@ common_reasoning_budget_state common_reasoning_budget_get_state(const struct lla
247247
}
248248
return ((const common_reasoning_budget_ctx *)smpl->ctx)->state;
249249
}
250+
251+
bool common_reasoning_budget_force(struct llama_sampler * smpl) {
252+
if (!smpl) {
253+
return false;
254+
}
255+
256+
auto * ctx = (common_reasoning_budget_ctx *) smpl->ctx;
257+
258+
// only a sampler that is actively counting down the budget may be forced;
259+
// any other state (idle, already forcing/waiting, or done) is left untouched
260+
if (ctx->state != REASONING_BUDGET_COUNTING) {
261+
return false;
262+
}
263+
264+
ctx->state = REASONING_BUDGET_FORCING;
265+
ctx->force_pos = 0;
266+
ctx->end_matcher.reset();
267+
LOG_INF("reasoning-budget: forced into forcing state (manual transition)\n");
268+
269+
return true;
270+
}

common/reasoning-budget.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,7 @@ struct llama_sampler * common_reasoning_budget_init(
4040
common_reasoning_budget_state initial_state = REASONING_BUDGET_IDLE);
4141

4242
common_reasoning_budget_state common_reasoning_budget_get_state(const struct llama_sampler * smpl);
43+
44+
// Manually transition the reasoning budget sampler into the FORCING state.
45+
// Returns true if the transition occurred.
46+
bool common_reasoning_budget_force(struct llama_sampler * smpl);

common/sampling.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,14 @@ uint32_t common_sampler_get_seed(const struct common_sampler * gsmpl) {
661661
return llama_sampler_get_seed(gsmpl->chain);
662662
}
663663

664+
bool common_sampler_reasoning_budget_force(struct common_sampler * gsmpl) {
665+
if (!gsmpl) {
666+
return false;
667+
}
668+
669+
return common_reasoning_budget_force(gsmpl->rbudget);
670+
}
671+
664672
// helpers
665673

666674
llama_token_data_array * common_sampler_get_candidates(struct common_sampler * gsmpl, bool do_sort) {

common/sampling.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ std::vector<llama_token> common_sampler_sample_and_accept_n(struct common_sample
8787

8888
uint32_t common_sampler_get_seed(const struct common_sampler * gsmpl);
8989

90+
// force the reasoning budget sampler (if any) to begin forcing its end sequence now.
91+
bool common_sampler_reasoning_budget_force(struct common_sampler * gsmpl);
92+
9093
// helpers
9194

9295
// access the internal list of current candidate tokens

tests/test-reasoning-budget.cpp

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,76 @@ static void test_reasoning_budget_clone_mid_forcing() {
184184
llama_sampler_free(sampler);
185185
}
186186

187+
static void test_reasoning_budget_force_manual() {
188+
const std::vector<llama_token> start = {100};
189+
const std::vector<llama_token> end = {101};
190+
const std::vector<llama_token> forced = {102, 101};
191+
192+
// if COUNTING, force() succeeds and begins forcing the end sequence from the start
193+
{
194+
auto * sampler = common_reasoning_budget_init(nullptr, start, end, forced, 5, REASONING_BUDGET_IDLE);
195+
196+
llama_sampler_accept(sampler, 100); // COUNTING, remaining=5
197+
llama_sampler_accept(sampler, 50); // COUNTING, remaining=4
198+
GGML_ASSERT(common_reasoning_budget_get_state(sampler) == REASONING_BUDGET_COUNTING);
199+
200+
GGML_ASSERT(common_reasoning_budget_force(sampler) && "force() should succeed from COUNTING");
201+
GGML_ASSERT(common_reasoning_budget_get_state(sampler) == REASONING_BUDGET_FORCING);
202+
203+
// forces the configured sequence from force_pos=0, then transitions to DONE
204+
GGML_ASSERT(get_forced_token(sampler, 102) == 102);
205+
llama_sampler_accept(sampler, 102);
206+
GGML_ASSERT(get_forced_token(sampler, 102) == 101);
207+
llama_sampler_accept(sampler, 101);
208+
GGML_ASSERT(common_reasoning_budget_get_state(sampler) == REASONING_BUDGET_DONE);
209+
210+
llama_sampler_free(sampler);
211+
}
212+
213+
// if IDLE, force() is a no-op
214+
{
215+
auto * sampler = common_reasoning_budget_init(nullptr, start, end, forced, 5, REASONING_BUDGET_IDLE);
216+
217+
GGML_ASSERT(!common_reasoning_budget_force(sampler) && "force() must not transition from IDLE");
218+
GGML_ASSERT(common_reasoning_budget_get_state(sampler) == REASONING_BUDGET_IDLE);
219+
220+
llama_sampler_free(sampler);
221+
}
222+
223+
// if DONE, force() is a no-op
224+
{
225+
auto * sampler = common_reasoning_budget_init(nullptr, start, end, forced, 5, REASONING_BUDGET_IDLE);
226+
227+
llama_sampler_accept(sampler, 100); // COUNTING
228+
llama_sampler_accept(sampler, 101); // natural end -> DONE
229+
GGML_ASSERT(common_reasoning_budget_get_state(sampler) == REASONING_BUDGET_DONE);
230+
231+
GGML_ASSERT(!common_reasoning_budget_force(sampler) && "force() must not transition from DONE");
232+
GGML_ASSERT(common_reasoning_budget_get_state(sampler) == REASONING_BUDGET_DONE);
233+
234+
llama_sampler_free(sampler);
235+
}
236+
237+
// if FORCING, force() is a no-op and must not rewind the force position
238+
{
239+
auto * sampler = common_reasoning_budget_init(nullptr, start, end, forced, 0, REASONING_BUDGET_FORCING);
240+
241+
GGML_ASSERT(get_forced_token(sampler, 102) == 102);
242+
llama_sampler_accept(sampler, 102); // advance to the second forced token (force_pos=1)
243+
244+
GGML_ASSERT(!common_reasoning_budget_force(sampler) && "force() must not transition from FORCING");
245+
GGML_ASSERT(common_reasoning_budget_get_state(sampler) == REASONING_BUDGET_FORCING);
246+
GGML_ASSERT(get_forced_token(sampler, 102) == 101 && "force() must not rewind the force position");
247+
248+
llama_sampler_free(sampler);
249+
}
250+
251+
// a null sampler is safely ignored
252+
GGML_ASSERT(!common_reasoning_budget_force(nullptr));
253+
254+
fprintf(stderr, " Test 'manual force transition' passed\n");
255+
}
256+
187257
// UTF-8 boundary detection unit test
188258
// Tests common_utf8_is_complete() from reasoning-budget.h
189259
static void test_utf8_boundary_detection() {
@@ -312,8 +382,9 @@ int main(void) {
312382

313383
test_reasoning_budget_clone_mid_counting();
314384
test_reasoning_budget_clone_mid_forcing();
385+
test_reasoning_budget_force_manual();
315386

316-
printf("OK (8 tests passed)\n");
387+
printf("OK (9 tests passed)\n");
317388

318389
printf("Testing UTF-8 boundary detection... ");
319390
test_utf8_boundary_detection();

0 commit comments

Comments
 (0)