Skip to content
Merged
20 changes: 19 additions & 1 deletion olive/evaluator/lmeval_ort.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,17 @@ def _normalize_provider_name(cls, ep: str) -> tuple[str, str]:
normalized_ep = str(ep).lower().replace("executionprovider", "")
return normalized_ep, cls._ORT_GENAI_PROVIDER_NAMES.get(normalized_ep, normalized_ep)

@staticmethod
def _resolve_past_present_share_buffer(override: bool | None, genai_config: dict) -> bool:
"""Resolve the ``past_present_share_buffer`` search option.

An explicit ``override`` takes precedence; otherwise fall back to the exported
``genai_config.json`` value (defaulting to ``False`` when absent).
"""
if override is not None:
return override
return genai_config.get("search", {}).get("past_present_share_buffer", False)

def __init__(
self,
pretrained: str,
Expand All @@ -553,6 +564,7 @@ def __init__(
ep: str = "follow_config",
ep_options: dict | None = None,
device: str = "cpu",
past_present_share_buffer: bool | None = None,
**kwargs,
):
"""Initialize the evaluator.
Expand All @@ -563,6 +575,9 @@ def __init__(
:param ep: The execution provider to use. "follow_config" will use the provider specified in the genai_config file
:param ep_options: The options to use for the execution provider. Only applicable if ep is not "follow_config"
:param device: The device to run log likelihood calculations on
:param past_present_share_buffer: Override the exported ``search.past_present_share_buffer`` setting. When
``None`` (default) the value from ``genai_config.json`` is used. Some models (e.g. Gemma 4) export with
shared buffers enabled but require it disabled for correct KV-cache handling during evaluation.
"""
if og is None:
raise ImportError("onnxruntime-genai is not installed.")
Expand Down Expand Up @@ -602,7 +617,10 @@ def __init__(
self._eot_token_id = self._eos_token_ids[0]
# Mirror the exported GenAI cache-sharing setting when creating GeneratorParams.
# Artifacts with shared past/present buffers require the same search option at runtime.
self._past_present_share_buffer = genai_config["search"].get("past_present_share_buffer", False)
# An explicit override (e.g. from a recipe config) takes precedence over the exported value.
self._past_present_share_buffer = self._resolve_past_present_share_buffer(
past_present_share_buffer, genai_config
)
self.params = og.GeneratorParams(self.model)
self.params.set_search_options(
max_length=self.max_length,
Expand Down
13 changes: 12 additions & 1 deletion olive/evaluator/olive_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2056,6 +2056,10 @@ def __init__(self, tasks: list[str], **kwargs):
self.ep = kwargs.get("execution_provider")
self.ep_options = kwargs.get("provider_options")
self.device = kwargs.get("device")
# Extra keyword arguments forwarded verbatim to the lm-eval model backend constructor
# (e.g. ``past_present_share_buffer`` for the ``ortgenai`` backend). Backend-specific;
# values here override the defaults Olive derives (batch_size, max_length, ep, ...).
self.model_args = kwargs.get("model_args") or {}
Comment thread
jiafatom marked this conversation as resolved.

def evaluate(
self,
Expand Down Expand Up @@ -2125,7 +2129,14 @@ def evaluate(
)

if self.tasks:
lmmodel = get_model(self.model_class)(**init_args, batch_size=self.batch_size, max_length=self.max_length)
model_init_args = {
**init_args,
"batch_size": self.batch_size,
"max_length": self.max_length,
# User-provided model_args win over the Olive-derived defaults above.
**self.model_args,
Comment thread
jiafatom marked this conversation as resolved.
}
lmmodel = get_model(self.model_class)(**model_init_args)

results = simple_evaluate(
model=lmmodel,
Expand Down
29 changes: 29 additions & 0 deletions test/evaluator/test_lmeval_ort.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,32 @@ class _Holder:

device_prop.fset(holder, "cpu")
assert device_prop.fget(holder) == "cpu"


class TestResolvePastPresentShareBuffer:
"""The exported genai_config value is the default; an explicit override wins."""

@staticmethod
def _resolve(override, genai_config):
from olive.evaluator.lmeval_ort import LMEvalORTGenAIEvaluator

# pylint: disable=protected-access
return LMEvalORTGenAIEvaluator._resolve_past_present_share_buffer(override, genai_config)

@pytest.mark.parametrize("config_value", [True, False])
def test_uses_config_value_when_no_override(self, config_value):
genai_config = {"search": {"past_present_share_buffer": config_value}}
assert self._resolve(None, genai_config) is config_value

def test_defaults_to_false_when_absent(self):
assert self._resolve(None, {"search": {}}) is False
assert self._resolve(None, {}) is False

@pytest.mark.parametrize(
("override", "config_value"),
[(False, True), (True, False)],
)
def test_override_takes_precedence_over_config(self, override, config_value):
# Gemma 4 exports with shared buffers enabled but requires it disabled for evaluation.
genai_config = {"search": {"past_present_share_buffer": config_value}}
assert self._resolve(override, genai_config) is override
33 changes: 33 additions & 0 deletions test/evaluator/test_olive_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,39 @@ def test_lm_evaluator_dispatches_to_requested_backend(

get_model_mock.assert_called_once_with(model_class)

@patch("lm_eval.utils.setup_logging")
@patch("lm_eval.tasks.TaskManager")
@patch("lm_eval.simple_evaluate")
@patch("lm_eval.api.registry.get_model")
def test_lm_evaluator_forwards_model_args_to_backend(
self, get_model_mock, simple_evaluate_mock, _task_manager_mock, _setup_logging_mock
):
from olive.evaluator.olive_evaluator import LMEvaluator
from olive.model.handler.onnx import ONNXModelHandler

simple_evaluate_mock.return_value = {"results": {}}
backend_ctor = MagicMock(return_value=MagicMock())
get_model_mock.return_value = backend_ctor

# model_args should reach the backend constructor and override Olive-derived defaults.
evaluator = LMEvaluator(
tasks=["arc_easy"],
model_class="ortgenai",
batch_size=1,
max_length=128,
model_args={"past_present_share_buffer": False, "batch_size": 4},
)

model = MagicMock(spec=ONNXModelHandler)
model.model_path = "/tmp/model.onnx"

evaluator.evaluate(model, metrics=[], device=Device.CPU, execution_providers=["CPUExecutionProvider"])

_, call_kwargs = backend_ctor.call_args
assert call_kwargs["past_present_share_buffer"] is False
assert call_kwargs["batch_size"] == 4
assert call_kwargs["max_length"] == 128
Comment thread
jiafatom marked this conversation as resolved.


@pytest.mark.skipif(
importlib.util.find_spec("lm_eval") is None,
Expand Down
Loading