Fix SemIfEngine.generate: sample from the low-level llama.cpp backend
This commit is contained in:
+41
-10
@@ -95,23 +95,54 @@ class SemIfEngine:
|
|||||||
def generate(
|
def generate(
|
||||||
self,
|
self,
|
||||||
messages: list[dict],
|
messages: list[dict],
|
||||||
temperature: float = 0.2,
|
temperature: float = 0.0,
|
||||||
max_tokens: int = 256,
|
max_tokens: int = 256,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Drive the pinned decision model in the normal way: text generation.
|
"""Drive the pinned decision model in the normal way: text generation.
|
||||||
|
|
||||||
SemIf scoring reads option logits directly; this instead uses the
|
SemIf scoring reads option logits directly; this instead autoregressively
|
||||||
underlying llama.cpp chat-completion endpoint on the same loaded model,
|
samples from the same llama.cpp context, e.g. for skill-tree authoring.
|
||||||
e.g. for skill-tree authoring. Each call resets the KV cache by
|
Generation decodes the chat template through the backend's low-level
|
||||||
default, so interleaving scoring and generation on one model is safe.
|
context (there is no high-level chat-completion object on the CPU
|
||||||
|
backend), stopping at the tokenizer's eos token. The KV cache is cleared
|
||||||
|
at the start, so interleaving scoring and generation on one model is safe.
|
||||||
"""
|
"""
|
||||||
model, tokenizer, metadata = self._ensure_loaded()
|
model, tokenizer, metadata = self._ensure_loaded()
|
||||||
try:
|
try:
|
||||||
reply = model.create_chat_completion(
|
import numpy
|
||||||
messages=messages,
|
|
||||||
temperature=temperature,
|
engine = model.engine
|
||||||
max_tokens=max_tokens,
|
prompt_ids = tokenizer.apply_chat_template(
|
||||||
|
messages,
|
||||||
|
tokenize=True,
|
||||||
|
add_generation_prompt=True,
|
||||||
|
enable_thinking=False,
|
||||||
)
|
)
|
||||||
|
engine.clear()
|
||||||
|
logits = engine._decode(prompt_ids, 0, 0, True)
|
||||||
|
generated: list[int] = []
|
||||||
|
rng = numpy.random.default_rng()
|
||||||
|
for position in range(max_tokens):
|
||||||
|
token = _sample_token(logits, temperature, rng)
|
||||||
|
if token == tokenizer.eos_token_id:
|
||||||
|
break
|
||||||
|
generated.append(token)
|
||||||
|
logits = engine._decode([token], len(prompt_ids) + position, 0, True)
|
||||||
|
return tokenizer.decode(generated).strip()
|
||||||
|
except EngineUnavailable:
|
||||||
|
raise
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise EngineUnavailable(f"generation failed: {exc}") from exc
|
raise EngineUnavailable(f"generation failed: {exc}") from exc
|
||||||
return reply["choices"][0]["message"]["content"].strip()
|
|
||||||
|
|
||||||
|
def _sample_token(logits, temperature: float, rng) -> int:
|
||||||
|
"""Pick the next token from next-position logits: greedy or temperature."""
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
if temperature <= 0.0:
|
||||||
|
return int(numpy.argmax(logits))
|
||||||
|
scaled = numpy.asarray(logits, dtype=numpy.float64) / max(temperature, 1e-6)
|
||||||
|
scaled = scaled - scaled.max()
|
||||||
|
probabilities = numpy.exp(scaled)
|
||||||
|
probabilities /= probabilities.sum()
|
||||||
|
return int(rng.choice(probabilities.size, p=probabilities))
|
||||||
|
|||||||
@@ -84,6 +84,12 @@ def test_generate_category_without_engine_raises():
|
|||||||
generate_category(engine, Request("anything"), {})
|
generate_category(engine, Request("anything"), {})
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_without_engine_raises():
|
||||||
|
engine = SemIfEngine(EngineConfig())
|
||||||
|
with pytest.raises(EngineUnavailable):
|
||||||
|
engine.generate([{"role": "user", "content": "hi"}])
|
||||||
|
|
||||||
|
|
||||||
def test_build_tree_includes_registry_stubs(tmp_path):
|
def test_build_tree_includes_registry_stubs(tmp_path):
|
||||||
registry = CategoryRegistry(str(tmp_path / "categories.json"))
|
registry = CategoryRegistry(str(tmp_path / "categories.json"))
|
||||||
registry.register("delivery", "Track and manage package deliveries.")
|
registry.register("delivery", "Track and manage package deliveries.")
|
||||||
|
|||||||
Reference in New Issue
Block a user