mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-31 04:07:52 +00:00
fix(ai): cap llama-server embedding batches at its 32-input request limit (#1281)
llama.cpp's llama-server rejects /v1/embeddings requests with more inputs than its launch --batch-size (default 32): "batch size 100 > maximum allowed batch size 32". gbrain sends batches of 100, so any page with >32 chunks fails to embed, and embed --stale then trips the Postgres statement_timeout retrying the doomed batches. The existing token-based protection (max_batch_tokens) can't bound item count — N tiny chunks fit under any token budget. Add an optional max_batch_items count cap to EmbeddingTouchpoint, enforced as a hard re-split after the token split in embed(), and set it to 32 on the llama-server recipe (replacing no_batch_cap: true, which wrongly assumed llama.cpp has no per-request item cap). A declared item cap also suppresses the missing-max_batch_tokens startup warning. Co-authored-by: Time Attakc <89218912+time-attack@users.noreply.github.com>
This commit is contained in:
+27
-1
@@ -599,6 +599,8 @@ function warnRecipesMissingBatchTokens(): void {
|
||||
// LiteLLM proxy, llama-server) — they ship without a static cap because
|
||||
// the cap depends on a user-launched server. Warning is noise for them.
|
||||
if (embedding.no_batch_cap === true) continue;
|
||||
// A declared item-count cap is a real batch cap — no warning needed.
|
||||
if (embedding.max_batch_items !== undefined) continue;
|
||||
if (_warnedRecipes.has(recipe.id)) continue;
|
||||
_warnedRecipes.add(recipe.id);
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -1517,10 +1519,17 @@ export async function embed(texts: string[], opts?: EmbedOpts): Promise<Float32A
|
||||
|
||||
// Pre-split is gated on max_batch_tokens. Recipes without it (e.g. OpenAI)
|
||||
// ride the fast path: one embedMany call, no recursion safety net.
|
||||
const batches = maxBatchTokens
|
||||
const tokenBatches = maxBatchTokens
|
||||
? splitByTokenBudget(truncated, Math.floor(maxBatchTokens * effectiveSafetyFactor(recipe)), charsPerToken)
|
||||
: [truncated];
|
||||
|
||||
// Hard COUNT cap (e.g. llama-server's "maximum allowed batch size 32").
|
||||
// Token budget can't bound item count, so re-split any oversized batch.
|
||||
const maxBatchItems = embedding?.max_batch_items;
|
||||
const batches = maxBatchItems
|
||||
? tokenBatches.flatMap(b => capBatchItems(b, maxBatchItems))
|
||||
: tokenBatches;
|
||||
|
||||
const allEmbeddings: Float32Array[] = [];
|
||||
let _embedThrew = false;
|
||||
try {
|
||||
@@ -1596,6 +1605,23 @@ export function splitByTokenBudget(
|
||||
return batches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a batch into sub-batches of at most `maxItems` inputs. Enforces a
|
||||
* hard COUNT cap that the token-budget split can't (many tiny inputs fit
|
||||
* under any token budget). Used for endpoints like llama.cpp's llama-server
|
||||
* that reject requests exceeding their launch batch size.
|
||||
*
|
||||
* @internal exported for tests; not part of the public gateway API.
|
||||
*/
|
||||
export function capBatchItems(texts: string[], maxItems: number): string[][] {
|
||||
if (maxItems <= 0 || texts.length <= maxItems) return [texts];
|
||||
const batches: string[][] = [];
|
||||
for (let i = 0; i < texts.length; i += maxItems) {
|
||||
batches.push(texts.slice(i, i + maxItems));
|
||||
}
|
||||
return batches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the error looks like a provider batch-token-limit error.
|
||||
*
|
||||
|
||||
@@ -35,9 +35,12 @@ export const llamaServer: Recipe = {
|
||||
trust_custom_dims: true, // #2271: user knows the launched model's native dim
|
||||
cost_per_1m_tokens_usd: 0,
|
||||
price_last_verified: '2026-05-10',
|
||||
// llama-server's batch capacity is set by `--ctx-size` at launch
|
||||
// time; no static cap to declare. v0.32 (#779).
|
||||
no_batch_cap: true,
|
||||
// llama-server enforces a hard request-COUNT cap equal to its launch
|
||||
// batch size (`--batch-size`, default 32): it rejects requests with
|
||||
// more inputs with `batch size N > maximum allowed batch size 32`.
|
||||
// The token-budget split can't bound item count, so cap it here. A
|
||||
// server launched with a larger `-b` can raise this. v0.32 (#779).
|
||||
max_batch_items: 32,
|
||||
},
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -54,6 +54,16 @@ export interface EmbeddingTouchpoint {
|
||||
* `max_batch_tokens` is also set.
|
||||
*/
|
||||
safety_factor?: number;
|
||||
/**
|
||||
* Maximum number of inputs per embedding request. Some endpoints enforce a
|
||||
* hard COUNT cap independent of token budget — notably llama.cpp's
|
||||
* `llama-server`, which rejects requests with more inputs than its launch
|
||||
* batch size (e.g. `batch size 100 > maximum allowed batch size 32`). The
|
||||
* token-budget pre-split cannot bound item count (many tiny chunks fit under
|
||||
* any token budget), so this is enforced as a separate hard re-split after
|
||||
* the token split. When unset, no count cap is applied.
|
||||
*/
|
||||
max_batch_items?: number;
|
||||
/**
|
||||
* v0.27.1: when true, at least one model in this recipe accepts image
|
||||
* inputs via a multimodal embedding endpoint (e.g. Voyage's
|
||||
|
||||
Reference in New Issue
Block a user