From eb9fa7116bf4063e619640c082cdb5cbf47e9aba Mon Sep 17 00:00:00 2001 From: William Arnold Date: Tue, 5 May 2026 14:14:29 -0700 Subject: [PATCH 1/5] Batch detokenization across positions in detokenize_top_logprobs_tokens Previously the top-k detokenization called batch_decode once per position. Flatten all top-k token ids across non-empty positions into a single batch_decode call and slice the decoded texts back, and skip the tokenizer entirely when decode_to_text is False. Adds unit tests. --- .../sglang/srt/managers/tokenizer_manager.py | 45 +++++-- test/manual/test_tokenizer_manager.py | 117 ++++++++++++++++++ 2 files changed, 150 insertions(+), 12 deletions(-) diff --git a/python/sglang/srt/managers/tokenizer_manager.py b/python/sglang/srt/managers/tokenizer_manager.py index aa7a5d95a2fe..2be45958fdc0 100644 --- a/python/sglang/srt/managers/tokenizer_manager.py +++ b/python/sglang/srt/managers/tokenizer_manager.py @@ -1793,18 +1793,39 @@ def detokenize_top_logprobs_tokens( token_logprobs_idx: List[int], decode_to_text: bool, ): - # TODO: The current implementation only batches the detokenization for top-k tokens per single position. - # We should batch all top-k tokens in all positions. - ret = [] - for i in range(len(token_logprobs_val)): - if token_logprobs_val[i]: - ret.append( - self.detokenize_logprob_tokens( - token_logprobs_val[i], token_logprobs_idx[i], decode_to_text - ) - ) - else: - ret.append(None) + ret: List[Optional[List]] = [None] * len(token_logprobs_val) + + if not decode_to_text: + for i, vals in enumerate(token_logprobs_val): + if vals: + ret[i] = [ + (logprob, token_id, None) + for logprob, token_id in zip(vals, token_logprobs_idx[i]) + ] + return ret + + assert self.tokenizer is not None + flat_ids: List[int] = [] + nonempty_positions: List[int] = [] + lengths: List[int] = [] + for i, vals in enumerate(token_logprobs_val): + if vals: + idxs = token_logprobs_idx[i] + flat_ids.extend(idxs) + lengths.append(len(idxs)) + nonempty_positions.append(i) + + if not flat_ids: + return ret + + decoded = self.tokenizer.batch_decode(flat_ids) + offset = 0 + for pos, length in zip(nonempty_positions, lengths): + chunk_texts = decoded[offset : offset + length] + offset += length + ret[pos] = list( + zip(token_logprobs_val[pos], token_logprobs_idx[pos], chunk_texts) + ) return ret def _calculate_spec_decoding_metrics( diff --git a/test/manual/test_tokenizer_manager.py b/test/manual/test_tokenizer_manager.py index 9525fedbb909..63dcea1d779f 100644 --- a/test/manual/test_tokenizer_manager.py +++ b/test/manual/test_tokenizer_manager.py @@ -404,5 +404,122 @@ def test_full_workflow_batch_strings(self): self.assertIsNone(result_token_type_ids) +class TestDetokenizeTopLogprobsTokens(unittest.TestCase): + """Test cases for detokenize_top_logprobs_tokens batched decoding. + + We avoid constructing a real TokenizerManager (which requires GPU-dependent + setup) and instead invoke the method as an unbound function against a bare + stand-in object that only exposes the attributes the method actually uses. + """ + + def setUp(self): + self.fn = TokenizerManager.detokenize_top_logprobs_tokens + self.stub = Mock(spec=["tokenizer"]) + self.stub.tokenizer = Mock() + self.stub.tokenizer.batch_decode = Mock( + side_effect=lambda ids: [f"tok_{i}" for i in ids] + ) + + def _call(self, vals, idxs, decode_to_text): + return self.fn(self.stub, vals, idxs, decode_to_text) + + def _reference_impl(self, vals, idxs, decode_to_text): + """Per-position reference — what the old implementation produced.""" + ret = [] + for i in range(len(vals)): + if vals[i]: + if not decode_to_text: + ret.append( + [(lp, tid, None) for lp, tid in zip(vals[i], idxs[i])] + ) + else: + texts = [f"tok_{tid}" for tid in idxs[i]] + ret.append(list(zip(vals[i], idxs[i], texts))) + else: + ret.append(None) + return ret + + def test_decode_to_text_false_skips_tokenizer(self): + """When decode_to_text=False, batch_decode should not be called.""" + vals = [[-0.1, -0.2], [-0.3]] + idxs = [[10, 20], [30]] + + result = self._call( + vals, idxs, decode_to_text=False + ) + + self.stub.tokenizer.batch_decode.assert_not_called() + self.assertEqual( + result, + [[(-0.1, 10, None), (-0.2, 20, None)], [(-0.3, 30, None)]], + ) + + def test_all_empty_positions_returns_nones(self): + """All-empty input returns [None, ...] and never calls batch_decode.""" + vals = [[], [], None] + idxs = [[], [], None] + + result = self._call( + vals, idxs, decode_to_text=True + ) + + self.assertEqual(result, [None, None, None]) + self.stub.tokenizer.batch_decode.assert_not_called() + + def test_empty_input_list(self): + """Zero-length input returns an empty list.""" + result = self._call( + [], [], decode_to_text=True + ) + self.assertEqual(result, []) + self.stub.tokenizer.batch_decode.assert_not_called() + + def test_mixed_empty_and_nonempty_positions(self): + """Texts are sliced back to the correct positions; empties stay None.""" + vals = [[-0.1, -0.2], [], [-0.5, -0.6, -0.7], None, [-0.9]] + idxs = [[10, 20], [], [30, 40, 50], None, [60]] + + result = self._call( + vals, idxs, decode_to_text=True + ) + + expected = [ + [(-0.1, 10, "tok_10"), (-0.2, 20, "tok_20")], + None, + [(-0.5, 30, "tok_30"), (-0.6, 40, "tok_40"), (-0.7, 50, "tok_50")], + None, + [(-0.9, 60, "tok_60")], + ] + self.assertEqual(result, expected) + + def test_single_batch_decode_call_with_flattened_ids(self): + """Efficiency guarantee: batch_decode is called once with flattened ids.""" + vals = [[-0.1, -0.2], [], [-0.5, -0.6, -0.7], [-0.9]] + idxs = [[10, 20], [], [30, 40, 50], [60]] + + self._call( + vals, idxs, decode_to_text=True + ) + + self.assertEqual( + self.stub.tokenizer.batch_decode.call_count, 1 + ) + (called_ids,), _ = self.stub.tokenizer.batch_decode.call_args + self.assertEqual(list(called_ids), [10, 20, 30, 40, 50, 60]) + + def test_matches_reference_per_position_implementation(self): + """Batched result is equivalent to per-position decoding.""" + vals = [[-0.1, -0.2, -0.3], [], [-0.5], None, [-0.7, -0.8]] + idxs = [[10, 20, 30], [], [40], None, [50, 60]] + + for decode_to_text in (True, False): + self.stub.tokenizer.batch_decode.reset_mock() + batched = self._call( + vals, idxs, decode_to_text=decode_to_text + ) + expected = self._reference_impl(vals, idxs, decode_to_text) + self.assertEqual(batched, expected) + + if __name__ == "__main__": unittest.main(verbosity=2) From f38d3e9a3390508e60608df248f9dfeb87235c1f Mon Sep 17 00:00:00 2001 From: William Arnold Date: Tue, 5 May 2026 14:23:26 -0700 Subject: [PATCH 2/5] Reuse detokenize_logprob_tokens in no-text branch Addresses PR review: the decode_to_text=False path duplicates detokenize_logprob_tokens's no-text logic. Delegate to it instead. Update the unit-test stub to expose detokenize_logprob_tokens so the production code path runs as written. --- python/sglang/srt/managers/tokenizer_manager.py | 7 +++---- test/manual/test_tokenizer_manager.py | 8 +++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/python/sglang/srt/managers/tokenizer_manager.py b/python/sglang/srt/managers/tokenizer_manager.py index 962319dc99d8..323ebe9e00ee 100644 --- a/python/sglang/srt/managers/tokenizer_manager.py +++ b/python/sglang/srt/managers/tokenizer_manager.py @@ -2029,10 +2029,9 @@ def detokenize_top_logprobs_tokens( if not decode_to_text: for i, vals in enumerate(token_logprobs_val): if vals: - ret[i] = [ - (logprob, token_id, None) - for logprob, token_id in zip(vals, token_logprobs_idx[i]) - ] + ret[i] = self.detokenize_logprob_tokens( + vals, token_logprobs_idx[i], decode_to_text + ) return ret assert self.tokenizer is not None diff --git a/test/manual/test_tokenizer_manager.py b/test/manual/test_tokenizer_manager.py index c8eaa72f9b70..50e90d6bfe9b 100644 --- a/test/manual/test_tokenizer_manager.py +++ b/test/manual/test_tokenizer_manager.py @@ -423,11 +423,17 @@ class TestDetokenizeTopLogprobsTokens(unittest.TestCase): def setUp(self): self.fn = TokenizerManager.detokenize_top_logprobs_tokens - self.stub = Mock(spec=["tokenizer"]) + self.stub = Mock(spec=["tokenizer", "detokenize_logprob_tokens"]) self.stub.tokenizer = Mock() self.stub.tokenizer.batch_decode = Mock( side_effect=lambda ids: [f"tok_{i}" for i in ids] ) + # Delegate to the real helper so we exercise the production path. + self.stub.detokenize_logprob_tokens = ( + lambda vals, idxs, decode_to_text: TokenizerManager.detokenize_logprob_tokens( + self.stub, vals, idxs, decode_to_text + ) + ) def _call(self, vals, idxs, decode_to_text): return self.fn(self.stub, vals, idxs, decode_to_text) From f9bbb886c2c071ce5f50c6c4bae2da66ca987405 Mon Sep 17 00:00:00 2001 From: William Arnold Date: Tue, 5 May 2026 14:35:38 -0700 Subject: [PATCH 3/5] Preserve single-token batch_decode shape --- python/sglang/srt/managers/tokenizer_manager.py | 2 +- test/manual/test_tokenizer_manager.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python/sglang/srt/managers/tokenizer_manager.py b/python/sglang/srt/managers/tokenizer_manager.py index 323ebe9e00ee..8d4570c64093 100644 --- a/python/sglang/srt/managers/tokenizer_manager.py +++ b/python/sglang/srt/managers/tokenizer_manager.py @@ -2048,7 +2048,7 @@ def detokenize_top_logprobs_tokens( if not flat_ids: return ret - decoded = self.tokenizer.batch_decode(flat_ids) + decoded = self.tokenizer.batch_decode([[idx] for idx in flat_ids]) offset = 0 for pos, length in zip(nonempty_positions, lengths): chunk_texts = decoded[offset : offset + length] diff --git a/test/manual/test_tokenizer_manager.py b/test/manual/test_tokenizer_manager.py index 50e90d6bfe9b..dd816d1fb1d1 100644 --- a/test/manual/test_tokenizer_manager.py +++ b/test/manual/test_tokenizer_manager.py @@ -426,7 +426,7 @@ def setUp(self): self.stub = Mock(spec=["tokenizer", "detokenize_logprob_tokens"]) self.stub.tokenizer = Mock() self.stub.tokenizer.batch_decode = Mock( - side_effect=lambda ids: [f"tok_{i}" for i in ids] + side_effect=lambda ids: [f"tok_{i[0]}" for i in ids] ) # Delegate to the real helper so we exercise the production path. self.stub.detokenize_logprob_tokens = ( @@ -508,7 +508,7 @@ def test_mixed_empty_and_nonempty_positions(self): self.assertEqual(result, expected) def test_single_batch_decode_call_with_flattened_ids(self): - """Efficiency guarantee: batch_decode is called once with flattened ids.""" + """Efficiency guarantee: batch_decode is called once with flattened token ids.""" vals = [[-0.1, -0.2], [], [-0.5, -0.6, -0.7], [-0.9]] idxs = [[10, 20], [], [30, 40, 50], [60]] @@ -520,7 +520,7 @@ def test_single_batch_decode_call_with_flattened_ids(self): self.stub.tokenizer.batch_decode.call_count, 1 ) (called_ids,), _ = self.stub.tokenizer.batch_decode.call_args - self.assertEqual(list(called_ids), [10, 20, 30, 40, 50, 60]) + self.assertEqual(list(called_ids), [[10], [20], [30], [40], [50], [60]]) def test_matches_reference_per_position_implementation(self): """Batched result is equivalent to per-position decoding.""" From 2985e842cf41b655677a83042e0542c98cb92bb4 Mon Sep 17 00:00:00 2001 From: William Arnold Date: Tue, 5 May 2026 14:43:31 -0700 Subject: [PATCH 4/5] Use tokenizer backend for batched logprob decode --- .../sglang/srt/managers/tokenizer_manager.py | 19 +++++++---- test/manual/test_tokenizer_manager.py | 33 +++++++++++++++++-- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/python/sglang/srt/managers/tokenizer_manager.py b/python/sglang/srt/managers/tokenizer_manager.py index 8d4570c64093..1c5d2e38d640 100644 --- a/python/sglang/srt/managers/tokenizer_manager.py +++ b/python/sglang/srt/managers/tokenizer_manager.py @@ -2011,13 +2011,20 @@ def detokenize_logprob_tokens( ] else: assert self.tokenizer is not None - # In transformers v5, batch_decode([1, 2, 3]) concatenates all tokens - # into one string. Wrap each ID in its own list so they decode separately. - token_texts = self.tokenizer.batch_decode( - [[idx] for idx in token_logprobs_idx] - ) + token_texts = self._batch_decode_token_ids(token_logprobs_idx) return list(zip(token_logprobs_val, token_logprobs_idx, token_texts)) + def _batch_decode_token_ids(self, token_ids: List[int]) -> List[str]: + # In transformers v5, batch_decode([1, 2, 3]) concatenates all tokens + # into one string. Wrap each ID in its own list so they decode separately. + token_id_seqs = [[idx] for idx in token_ids] + backend_tokenizer = getattr(self.tokenizer, "backend_tokenizer", None) + if backend_tokenizer is not None and hasattr(backend_tokenizer, "decode_batch"): + return backend_tokenizer.decode_batch( + token_id_seqs, skip_special_tokens=False + ) + return self.tokenizer.batch_decode(token_id_seqs) + def detokenize_top_logprobs_tokens( self, token_logprobs_val: List[float], @@ -2048,7 +2055,7 @@ def detokenize_top_logprobs_tokens( if not flat_ids: return ret - decoded = self.tokenizer.batch_decode([[idx] for idx in flat_ids]) + decoded = self._batch_decode_token_ids(flat_ids) offset = 0 for pos, length in zip(nonempty_positions, lengths): chunk_texts = decoded[offset : offset + length] diff --git a/test/manual/test_tokenizer_manager.py b/test/manual/test_tokenizer_manager.py index dd816d1fb1d1..c5ef5a55b27e 100644 --- a/test/manual/test_tokenizer_manager.py +++ b/test/manual/test_tokenizer_manager.py @@ -423,11 +423,19 @@ class TestDetokenizeTopLogprobsTokens(unittest.TestCase): def setUp(self): self.fn = TokenizerManager.detokenize_top_logprobs_tokens - self.stub = Mock(spec=["tokenizer", "detokenize_logprob_tokens"]) - self.stub.tokenizer = Mock() + self.stub = Mock( + spec=["tokenizer", "detokenize_logprob_tokens", "_batch_decode_token_ids"] + ) + self.stub.tokenizer = Mock(spec=["batch_decode", "backend_tokenizer"]) + self.stub.tokenizer.backend_tokenizer = None self.stub.tokenizer.batch_decode = Mock( side_effect=lambda ids: [f"tok_{i[0]}" for i in ids] ) + self.stub._batch_decode_token_ids = ( + lambda token_ids: TokenizerManager._batch_decode_token_ids( + self.stub, token_ids + ) + ) # Delegate to the real helper so we exercise the production path. self.stub.detokenize_logprob_tokens = ( lambda vals, idxs, decode_to_text: TokenizerManager.detokenize_logprob_tokens( @@ -522,6 +530,27 @@ def test_single_batch_decode_call_with_flattened_ids(self): (called_ids,), _ = self.stub.tokenizer.batch_decode.call_args self.assertEqual(list(called_ids), [[10], [20], [30], [40], [50], [60]]) + def test_prefers_backend_decode_batch_when_available(self): + """Use the tokenizer backend to avoid per-sequence Python decode loops.""" + vals = [[-0.1, -0.2], [], [-0.5, -0.6, -0.7], [-0.9]] + idxs = [[10, 20], [], [30, 40, 50], [60]] + backend_tokenizer = Mock(spec=["decode_batch"]) + backend_tokenizer.decode_batch = Mock( + side_effect=lambda ids, skip_special_tokens: [ + f"tok_{i[0]}_{skip_special_tokens}" for i in ids + ] + ) + self.stub.tokenizer.backend_tokenizer = backend_tokenizer + + result = self._call(vals, idxs, decode_to_text=True) + + self.stub.tokenizer.batch_decode.assert_not_called() + backend_tokenizer.decode_batch.assert_called_once_with( + [[10], [20], [30], [40], [50], [60]], skip_special_tokens=False + ) + self.assertEqual(result[0][0], (-0.1, 10, "tok_10_False")) + self.assertEqual(result[2][2], (-0.7, 50, "tok_50_False")) + def test_matches_reference_per_position_implementation(self): """Batched result is equivalent to per-position decoding.""" vals = [[-0.1, -0.2, -0.3], [], [-0.5], None, [-0.7, -0.8]] From 3fab1003f9cc0aa2879fad943d07af8a0ec17e75 Mon Sep 17 00:00:00 2001 From: William Arnold Date: Mon, 11 May 2026 15:01:38 -0700 Subject: [PATCH 5/5] Clean up batched logprob decode tests --- .../sglang/srt/managers/tokenizer_manager.py | 20 +++---- test/manual/test_tokenizer_manager.py | 53 +++++++------------ 2 files changed, 30 insertions(+), 43 deletions(-) diff --git a/python/sglang/srt/managers/tokenizer_manager.py b/python/sglang/srt/managers/tokenizer_manager.py index 1c5d2e38d640..2a2ce80d821e 100644 --- a/python/sglang/srt/managers/tokenizer_manager.py +++ b/python/sglang/srt/managers/tokenizer_manager.py @@ -2009,12 +2009,13 @@ def detokenize_logprob_tokens( (logprob, token_id, None) for logprob, token_id in zip(token_logprobs_val, token_logprobs_idx) ] - else: - assert self.tokenizer is not None - token_texts = self._batch_decode_token_ids(token_logprobs_idx) - return list(zip(token_logprobs_val, token_logprobs_idx, token_texts)) + + assert self.tokenizer is not None + token_texts = self._batch_decode_token_ids(token_logprobs_idx) + return list(zip(token_logprobs_val, token_logprobs_idx, token_texts)) def _batch_decode_token_ids(self, token_ids: List[int]) -> List[str]: + assert self.tokenizer is not None # In transformers v5, batch_decode([1, 2, 3]) concatenates all tokens # into one string. Wrap each ID in its own list so they decode separately. token_id_seqs = [[idx] for idx in token_ids] @@ -2027,8 +2028,8 @@ def _batch_decode_token_ids(self, token_ids: List[int]) -> List[str]: def detokenize_top_logprobs_tokens( self, - token_logprobs_val: List[float], - token_logprobs_idx: List[int], + token_logprobs_val: List[Optional[List[float]]], + token_logprobs_idx: List[Optional[List[int]]], decode_to_text: bool, ): ret: List[Optional[List]] = [None] * len(token_logprobs_val) @@ -2036,9 +2037,9 @@ def detokenize_top_logprobs_tokens( if not decode_to_text: for i, vals in enumerate(token_logprobs_val): if vals: - ret[i] = self.detokenize_logprob_tokens( - vals, token_logprobs_idx[i], decode_to_text - ) + idxs = token_logprobs_idx[i] + assert idxs is not None + ret[i] = self.detokenize_logprob_tokens(vals, idxs, decode_to_text) return ret assert self.tokenizer is not None @@ -2048,6 +2049,7 @@ def detokenize_top_logprobs_tokens( for i, vals in enumerate(token_logprobs_val): if vals: idxs = token_logprobs_idx[i] + assert idxs is not None flat_ids.extend(idxs) lengths.append(len(idxs)) nonempty_positions.append(i) diff --git a/test/manual/test_tokenizer_manager.py b/test/manual/test_tokenizer_manager.py index c5ef5a55b27e..b4474b19063c 100644 --- a/test/manual/test_tokenizer_manager.py +++ b/test/manual/test_tokenizer_manager.py @@ -431,30 +431,29 @@ def setUp(self): self.stub.tokenizer.batch_decode = Mock( side_effect=lambda ids: [f"tok_{i[0]}" for i in ids] ) - self.stub._batch_decode_token_ids = ( - lambda token_ids: TokenizerManager._batch_decode_token_ids( - self.stub, token_ids - ) - ) - # Delegate to the real helper so we exercise the production path. - self.stub.detokenize_logprob_tokens = ( - lambda vals, idxs, decode_to_text: TokenizerManager.detokenize_logprob_tokens( + + def batch_decode_token_ids(token_ids): + return TokenizerManager._batch_decode_token_ids(self.stub, token_ids) + + def detokenize_logprob_tokens(vals, idxs, decode_to_text): + return TokenizerManager.detokenize_logprob_tokens( self.stub, vals, idxs, decode_to_text ) - ) + + self.stub._batch_decode_token_ids = batch_decode_token_ids + # Delegate to the real helper so we exercise the production path. + self.stub.detokenize_logprob_tokens = detokenize_logprob_tokens def _call(self, vals, idxs, decode_to_text): return self.fn(self.stub, vals, idxs, decode_to_text) def _reference_impl(self, vals, idxs, decode_to_text): - """Per-position reference — what the old implementation produced.""" + """Per-position reference for what the old implementation produced.""" ret = [] for i in range(len(vals)): if vals[i]: if not decode_to_text: - ret.append( - [(lp, tid, None) for lp, tid in zip(vals[i], idxs[i])] - ) + ret.append([(lp, tid, None) for lp, tid in zip(vals[i], idxs[i])]) else: texts = [f"tok_{tid}" for tid in idxs[i]] ret.append(list(zip(vals[i], idxs[i], texts))) @@ -467,9 +466,7 @@ def test_decode_to_text_false_skips_tokenizer(self): vals = [[-0.1, -0.2], [-0.3]] idxs = [[10, 20], [30]] - result = self._call( - vals, idxs, decode_to_text=False - ) + result = self._call(vals, idxs, decode_to_text=False) self.stub.tokenizer.batch_decode.assert_not_called() self.assertEqual( @@ -482,18 +479,14 @@ def test_all_empty_positions_returns_nones(self): vals = [[], [], None] idxs = [[], [], None] - result = self._call( - vals, idxs, decode_to_text=True - ) + result = self._call(vals, idxs, decode_to_text=True) self.assertEqual(result, [None, None, None]) self.stub.tokenizer.batch_decode.assert_not_called() def test_empty_input_list(self): """Zero-length input returns an empty list.""" - result = self._call( - [], [], decode_to_text=True - ) + result = self._call([], [], decode_to_text=True) self.assertEqual(result, []) self.stub.tokenizer.batch_decode.assert_not_called() @@ -502,9 +495,7 @@ def test_mixed_empty_and_nonempty_positions(self): vals = [[-0.1, -0.2], [], [-0.5, -0.6, -0.7], None, [-0.9]] idxs = [[10, 20], [], [30, 40, 50], None, [60]] - result = self._call( - vals, idxs, decode_to_text=True - ) + result = self._call(vals, idxs, decode_to_text=True) expected = [ [(-0.1, 10, "tok_10"), (-0.2, 20, "tok_20")], @@ -520,13 +511,9 @@ def test_single_batch_decode_call_with_flattened_ids(self): vals = [[-0.1, -0.2], [], [-0.5, -0.6, -0.7], [-0.9]] idxs = [[10, 20], [], [30, 40, 50], [60]] - self._call( - vals, idxs, decode_to_text=True - ) + self._call(vals, idxs, decode_to_text=True) - self.assertEqual( - self.stub.tokenizer.batch_decode.call_count, 1 - ) + self.assertEqual(self.stub.tokenizer.batch_decode.call_count, 1) (called_ids,), _ = self.stub.tokenizer.batch_decode.call_args self.assertEqual(list(called_ids), [[10], [20], [30], [40], [50], [60]]) @@ -558,9 +545,7 @@ def test_matches_reference_per_position_implementation(self): for decode_to_text in (True, False): self.stub.tokenizer.batch_decode.reset_mock() - batched = self._call( - vals, idxs, decode_to_text=decode_to_text - ) + batched = self._call(vals, idxs, decode_to_text=decode_to_text) expected = self._reference_impl(vals, idxs, decode_to_text) self.assertEqual(batched, expected)