Skip to content

Commit d9bd783

Browse files
test: Add model inference tests for Qwen3CrossEncoder (#246)
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent 465e700 commit d9bd783

1 file changed

Lines changed: 119 additions & 0 deletions

File tree

tests/test_qwen3_reranker.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
"""Unit tests for Qwen3CrossEncoder model registration and scoring logic."""
22

3+
from unittest.mock import MagicMock, patch
4+
35
import numpy as np
6+
import pytest
47

8+
from qwen3_embed.common.onnx_model import OnnxOutputContext
59
from qwen3_embed.rerank.cross_encoder.qwen3_cross_encoder import (
610
DEFAULT_INSTRUCTION,
711
SYSTEM_PROMPT,
@@ -197,3 +201,118 @@ def test_token_ids_are_positive(self):
197201

198202
def test_token_ids_are_different(self):
199203
assert TOKEN_YES_ID != TOKEN_NO_ID
204+
205+
206+
@pytest.fixture
207+
def mocked_qwen3_encoder():
208+
"""Returns a Qwen3CrossEncoder with mocked model and tokenizer."""
209+
with patch(
210+
"qwen3_embed.rerank.cross_encoder.qwen3_cross_encoder.Qwen3CrossEncoder.download_model",
211+
return_value="/tmp/mock",
212+
):
213+
encoder = Qwen3CrossEncoder("n24q02m/Qwen3-Reranker-0.6B-ONNX-YesNo", lazy_load=True)
214+
215+
encoder.model = MagicMock()
216+
# Mocking output of the ONNX model to be (batch=1, 2) shape
217+
encoder.model.run.return_value = [np.array([[-10.0, 10.0]], dtype=np.float32)]
218+
encoder.model_input_names = {"input_ids", "attention_mask"}
219+
220+
mock_tokenizer = MagicMock()
221+
mock_encoding = MagicMock()
222+
mock_encoding.ids = [1, 2, 3]
223+
mock_encoding.attention_mask = [1, 1, 1]
224+
mock_tokenizer.encode_batch.return_value = [mock_encoding]
225+
encoder.tokenizer = mock_tokenizer
226+
227+
return encoder
228+
229+
230+
class TestQwen3CrossEncoderInference:
231+
"""Verify ONNX embedding methods override."""
232+
233+
def test_onnx_embed_texts_missing_model(self):
234+
with patch(
235+
"qwen3_embed.rerank.cross_encoder.qwen3_cross_encoder.Qwen3CrossEncoder.download_model",
236+
return_value="/tmp/mock",
237+
):
238+
encoder = Qwen3CrossEncoder("n24q02m/Qwen3-Reranker-0.6B-ONNX-YesNo", lazy_load=True)
239+
encoder.model = None
240+
with pytest.raises(ValueError, match="Model not loaded"):
241+
encoder._onnx_embed_texts(["text1"])
242+
243+
def test_onnx_embed_texts_missing_tokenizer(self):
244+
with patch(
245+
"qwen3_embed.rerank.cross_encoder.qwen3_cross_encoder.Qwen3CrossEncoder.download_model",
246+
return_value="/tmp/mock",
247+
):
248+
encoder = Qwen3CrossEncoder("n24q02m/Qwen3-Reranker-0.6B-ONNX-YesNo", lazy_load=True)
249+
encoder.model = MagicMock()
250+
encoder.tokenizer = None
251+
with pytest.raises(AssertionError, match="Tokenizer not loaded"):
252+
encoder._onnx_embed_texts(["text1"])
253+
254+
def test_onnx_embed_texts_success(self, mocked_qwen3_encoder):
255+
ctx = mocked_qwen3_encoder._onnx_embed_texts(["hello world"])
256+
assert isinstance(ctx, OnnxOutputContext)
257+
assert ctx.model_output.shape == (1,)
258+
# -10.0, 10.0 should give high probability
259+
assert ctx.model_output[0] > 0.99
260+
mocked_qwen3_encoder.model.run.assert_called_once()
261+
mocked_qwen3_encoder.tokenizer.encode_batch.assert_called_once_with(["hello world"])
262+
263+
def test_onnx_embed_texts_multiple(self, mocked_qwen3_encoder):
264+
# We simulate tokenizer returning for a single text, because _onnx_embed_texts loops text by text
265+
ctx = mocked_qwen3_encoder._onnx_embed_texts(["text1", "text2"])
266+
assert ctx.model_output.shape == (2,)
267+
assert mocked_qwen3_encoder.model.run.call_count == 2
268+
assert mocked_qwen3_encoder.tokenizer.encode_batch.call_count == 2
269+
270+
def test_onnx_embed_pairs_success(self, mocked_qwen3_encoder):
271+
ctx = mocked_qwen3_encoder.onnx_embed_pairs([("Query1", "Doc1"), ("Query2", "Doc2")])
272+
assert ctx.model_output.shape == (2,)
273+
assert mocked_qwen3_encoder.model.run.call_count == 2
274+
assert mocked_qwen3_encoder.tokenizer.encode_batch.call_count == 2
275+
276+
# Verify chat template formatting
277+
calls = mocked_qwen3_encoder.tokenizer.encode_batch.call_args_list
278+
# Call 1
279+
text1 = calls[0][0][0][0]
280+
assert "<Query>: Query1" in text1
281+
assert "<Document>: Doc1" in text1
282+
# Call 2
283+
text2 = calls[1][0][0][0]
284+
assert "<Query>: Query2" in text2
285+
assert "<Document>: Doc2" in text2
286+
287+
def test_onnx_embed_success(self, mocked_qwen3_encoder):
288+
ctx = mocked_qwen3_encoder.onnx_embed("Query", ["Doc1", "Doc2"])
289+
assert ctx.model_output.shape == (2,)
290+
assert mocked_qwen3_encoder.model.run.call_count == 2
291+
assert mocked_qwen3_encoder.tokenizer.encode_batch.call_count == 2
292+
293+
calls = mocked_qwen3_encoder.tokenizer.encode_batch.call_args_list
294+
text1 = calls[0][0][0][0]
295+
assert "<Query>: Query" in text1
296+
assert "<Document>: Doc1" in text1
297+
298+
text2 = calls[1][0][0][0]
299+
assert "<Query>: Query" in text2
300+
assert "<Document>: Doc2" in text2
301+
302+
def test_onnx_embed_custom_instruction(self, mocked_qwen3_encoder):
303+
ctx = mocked_qwen3_encoder.onnx_embed("Query", ["Doc"], instruction="Custom Instruction!")
304+
assert ctx.model_output.shape == (1,)
305+
306+
calls = mocked_qwen3_encoder.tokenizer.encode_batch.call_args_list
307+
text1 = calls[0][0][0][0]
308+
assert "<Instruct>: Custom Instruction!" in text1
309+
310+
def test_onnx_embed_pairs_custom_instruction(self, mocked_qwen3_encoder):
311+
ctx = mocked_qwen3_encoder.onnx_embed_pairs(
312+
[("Query", "Doc")], instruction="Custom Pair Instruction!"
313+
)
314+
assert ctx.model_output.shape == (1,)
315+
316+
calls = mocked_qwen3_encoder.tokenizer.encode_batch.call_args_list
317+
text1 = calls[0][0][0][0]
318+
assert "<Instruct>: Custom Pair Instruction!" in text1

0 commit comments

Comments
 (0)