Spaces:
Running on Zero
Running on Zero
Preserve instructions in large code prompts
Browse files- app.py +30 -7
- generation.py +22 -0
- test_generation.py +17 -1
app.py
CHANGED
|
@@ -36,7 +36,7 @@ from transformers import (
|
|
| 36 |
StoppingCriteria,
|
| 37 |
StoppingCriteriaList,
|
| 38 |
)
|
| 39 |
-
from generation import merge_eos_token_ids
|
| 40 |
from openai_compat import (
|
| 41 |
indexed_tool_calls,
|
| 42 |
normalize_messages,
|
|
@@ -62,12 +62,10 @@ MODEL = os.getenv(
|
|
| 62 |
|
| 63 |
MAX_CONTEXT_TOKENS = int(os.getenv("MAX_CONTEXT_TOKENS", "16384"))
|
| 64 |
MAX_NEW_TOKENS = int(os.getenv("MAX_NEW_TOKENS", "1024"))
|
|
|
|
| 65 |
TOOL_PROTOCOL_MARKER = "OPENAI TOOL CALL FORMAT — MANDATORY"
|
| 66 |
|
| 67 |
-
# Keep the most recent tool result and user instructions if a client sends a
|
| 68 |
-
# conversation longer than this Space can serve reliably.
|
| 69 |
tokenizer = AutoTokenizer.from_pretrained(MODEL)
|
| 70 |
-
tokenizer.truncation_side = "left"
|
| 71 |
|
| 72 |
print(f"Loading {MODEL} on ZeroGPU during startup...", flush=True)
|
| 73 |
model = AutoModelForCausalLM.from_pretrained(
|
|
@@ -171,9 +169,34 @@ def gerar(
|
|
| 171 |
prompt,
|
| 172 |
return_tensors="pt",
|
| 173 |
add_special_tokens=False,
|
| 174 |
-
truncation=
|
| 175 |
-
|
| 176 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
|
| 178 |
print(
|
| 179 |
f"Generation started: input_tokens={inputs['input_ids'].shape[1]} "
|
|
|
|
| 36 |
StoppingCriteria,
|
| 37 |
StoppingCriteriaList,
|
| 38 |
)
|
| 39 |
+
from generation import head_tail_token_counts, merge_eos_token_ids
|
| 40 |
from openai_compat import (
|
| 41 |
indexed_tool_calls,
|
| 42 |
normalize_messages,
|
|
|
|
| 62 |
|
| 63 |
MAX_CONTEXT_TOKENS = int(os.getenv("MAX_CONTEXT_TOKENS", "16384"))
|
| 64 |
MAX_NEW_TOKENS = int(os.getenv("MAX_NEW_TOKENS", "1024"))
|
| 65 |
+
PRESERVED_PREFIX_TOKENS = int(os.getenv("PRESERVED_PREFIX_TOKENS", "4096"))
|
| 66 |
TOOL_PROTOCOL_MARKER = "OPENAI TOOL CALL FORMAT — MANDATORY"
|
| 67 |
|
|
|
|
|
|
|
| 68 |
tokenizer = AutoTokenizer.from_pretrained(MODEL)
|
|
|
|
| 69 |
|
| 70 |
print(f"Loading {MODEL} on ZeroGPU during startup...", flush=True)
|
| 71 |
model = AutoModelForCausalLM.from_pretrained(
|
|
|
|
| 169 |
prompt,
|
| 170 |
return_tensors="pt",
|
| 171 |
add_special_tokens=False,
|
| 172 |
+
truncation=False,
|
| 173 |
+
)
|
| 174 |
+
input_budget = max(1, MAX_CONTEXT_TOKENS - output_tokens)
|
| 175 |
+
input_length = inputs["input_ids"].shape[1]
|
| 176 |
+
if input_length > input_budget:
|
| 177 |
+
head_tokens, tail_tokens = head_tail_token_counts(
|
| 178 |
+
input_length,
|
| 179 |
+
input_budget,
|
| 180 |
+
PRESERVED_PREFIX_TOKENS,
|
| 181 |
+
)
|
| 182 |
+
for key, value in inputs.items():
|
| 183 |
+
if (
|
| 184 |
+
isinstance(value, torch.Tensor)
|
| 185 |
+
and value.ndim == 2
|
| 186 |
+
and value.shape[1] == input_length
|
| 187 |
+
):
|
| 188 |
+
parts = []
|
| 189 |
+
if head_tokens:
|
| 190 |
+
parts.append(value[:, :head_tokens])
|
| 191 |
+
if tail_tokens:
|
| 192 |
+
parts.append(value[:, -tail_tokens:])
|
| 193 |
+
inputs[key] = torch.cat(parts, dim=1)
|
| 194 |
+
print(
|
| 195 |
+
f"Prompt truncated head+tail: original={input_length} "
|
| 196 |
+
f"head={head_tokens} tail={tail_tokens}",
|
| 197 |
+
flush=True,
|
| 198 |
+
)
|
| 199 |
+
inputs = inputs.to("cuda")
|
| 200 |
|
| 201 |
print(
|
| 202 |
f"Generation started: input_tokens={inputs['input_ids'].shape[1]} "
|
generation.py
CHANGED
|
@@ -5,6 +5,28 @@ from __future__ import annotations
|
|
| 5 |
from collections.abc import Iterable
|
| 6 |
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
def ensure_bos_token(prompt: str, bos_token: str | None) -> str:
|
| 9 |
"""Prefix the model's BOS token when the chat template omits it."""
|
| 10 |
if not bos_token or prompt.startswith(bos_token):
|
|
|
|
| 5 |
from collections.abc import Iterable
|
| 6 |
|
| 7 |
|
| 8 |
+
def head_tail_token_counts(
|
| 9 |
+
total_tokens: int,
|
| 10 |
+
token_budget: int,
|
| 11 |
+
preserved_prefix_tokens: int,
|
| 12 |
+
) -> tuple[int, int]:
|
| 13 |
+
"""Split an oversized prompt budget between its prefix and recent tail.
|
| 14 |
+
|
| 15 |
+
Keeping only the tail can erase system instructions, tool definitions, and
|
| 16 |
+
a task stated before a large code block. Keeping a bounded prefix plus the
|
| 17 |
+
largest possible tail retains both the operating contract and the newest
|
| 18 |
+
conversation state.
|
| 19 |
+
"""
|
| 20 |
+
total = max(0, int(total_tokens))
|
| 21 |
+
budget = max(1, int(token_budget))
|
| 22 |
+
if total <= budget:
|
| 23 |
+
return total, 0
|
| 24 |
+
|
| 25 |
+
prefix = max(0, int(preserved_prefix_tokens))
|
| 26 |
+
head = min(prefix, budget - 1)
|
| 27 |
+
return head, budget - head
|
| 28 |
+
|
| 29 |
+
|
| 30 |
def ensure_bos_token(prompt: str, bos_token: str | None) -> str:
|
| 31 |
"""Prefix the model's BOS token when the chat template omits it."""
|
| 32 |
if not bos_token or prompt.startswith(bos_token):
|
test_generation.py
CHANGED
|
@@ -4,7 +4,11 @@ from __future__ import annotations
|
|
| 4 |
|
| 5 |
import unittest
|
| 6 |
|
| 7 |
-
from generation import
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
|
| 10 |
class GenerationConfigTests(unittest.TestCase):
|
|
@@ -30,6 +34,18 @@ class GenerationConfigTests(unittest.TestCase):
|
|
| 30 |
def test_returns_none_without_valid_ids(self) -> None:
|
| 31 |
self.assertIsNone(merge_eos_token_ids(None, None))
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
if __name__ == "__main__":
|
| 35 |
unittest.main()
|
|
|
|
| 4 |
|
| 5 |
import unittest
|
| 6 |
|
| 7 |
+
from generation import (
|
| 8 |
+
ensure_bos_token,
|
| 9 |
+
head_tail_token_counts,
|
| 10 |
+
merge_eos_token_ids,
|
| 11 |
+
)
|
| 12 |
|
| 13 |
|
| 14 |
class GenerationConfigTests(unittest.TestCase):
|
|
|
|
| 34 |
def test_returns_none_without_valid_ids(self) -> None:
|
| 35 |
self.assertIsNone(merge_eos_token_ids(None, None))
|
| 36 |
|
| 37 |
+
def test_short_prompt_is_not_split(self) -> None:
|
| 38 |
+
self.assertEqual(head_tail_token_counts(100, 200, 50), (100, 0))
|
| 39 |
+
|
| 40 |
+
def test_large_prompt_preserves_prefix_and_maximizes_tail(self) -> None:
|
| 41 |
+
self.assertEqual(
|
| 42 |
+
head_tail_token_counts(30_000, 16_000, 4_096),
|
| 43 |
+
(4_096, 11_904),
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
def test_tiny_budget_still_preserves_latest_token(self) -> None:
|
| 47 |
+
self.assertEqual(head_tail_token_counts(100, 1, 4_096), (0, 1))
|
| 48 |
+
|
| 49 |
|
| 50 |
if __name__ == "__main__":
|
| 51 |
unittest.main()
|