fix: support large input texts with chunking - #100
RSKKSOFFICIAL wants to merge 18 commits into
Conversation
Signed-off-by: RSKKSOFFICIAL <rsksofficial02@gmail.com>
Signed-off-by: RSKKSOFFICIAL <rsksofficial02@gmail.com>
Signed-off-by: RSKKSOFFICIAL <rsksofficial02@gmail.com>
| self.config["inference"].get("repetition_penalty", 1.0), min_repetition_penalty | ||
| ), | ||
| } | ||
| results = self.translator.translate_batch( |
There was a problem hiding this comment.
Would be nice to use one batch for all chunks, instead of running one batch per chunk in sequence
| logger = logging.getLogger(os.environ["APP_ID"] + __name__) | ||
|
|
||
| # Languages that do not use spaces between words — join chunks without a space separator | ||
| _NO_SPACE_LANGUAGES = {"zh", "ja", "th", "my", "km", "lo", "bo"} |
There was a problem hiding this comment.
What about yue (Cantonese), dz (Dzongkha — same script as the included bo), shn (Shan)?
There was a problem hiding this comment.
Good point. yue, dz, and shn also use writing systems where spaces shouldn't be inserted between chunks. I'll add them to _NO_SPACE_LANGUAGES.
btw, are their any more languages which uses no space?
Signed-off-by: RSKKSOFFICIAL <rsksofficial02@gmail.com>
|
Hi @marcelklehr, I’ve addressed the review comments and tested the changes locally. Everything is working as expected on my side. Could you please take another look and let me know if there’s anything else you’d like me to adjust? |
Signed-off-by: RSKKSOFFICIAL <rsksofficial02@gmail.com>
|
Hello there, We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process. Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6 Thank you for contributing to Nextcloud and we hope to hear from you soon! (If you believe you should not receive this message, you can add yourself to the blocklist.) |
kyteinsky
left a comment
There was a problem hiding this comment.
thanks for the PR, looks good overall but posted some comments maybe we can discuss about.
did you try out translate_iterable? It was mentioned in the original issue: https://opennmt.net/CTranslate2/python/ctranslate2.Translator.html#ctranslate2.Translator.translate_iterable
it seems to have a few upsides like parallel translation (which we don't do here I think) and parallel prefetching, but should simplify the implementation here at least.
Co-authored-by: Anupam Kumar <kyteinsky@gmail.com> Signed-off-by: Ravi Shankar Kumar <154051646+RSKKSOFFICIAL@users.noreply.github.com>
Signed-off-by: RSKKSOFFICIAL <rsksofficial02@gmail.com>
for more information, see https://pre-commit.ci
|
Hi @marcelklehr and @kyteinsky, Thank you both for the helpful feedback and suggestions. I’ve addressed the review comments and pushed the updated changes. I’ve also tested the updated implementation locally, including long inputs and the relevant language cases, and everything is working as expected on my side. When you have a chance, could you please take another look at the changes? I’d really appreciate your feedback, and I’d be happy to make any further adjustments if needed. |
kyteinsky
left a comment
There was a problem hiding this comment.
thanks for the fixes, there are a few more things I noticed in the implementation that would be good to discuss about.
feel free to ask questions or push back on the comments.
sorry that the review is taking this much time.
| # Sentence-boundary split: keep the delimiter attached to the preceding sentence. | ||
| # For no-space text (CJK etc.) use \s* because sentences run together without | ||
| # whitespace. For all other text use \s+ to avoid splitting on abbreviations, | ||
| # decimals, URLs, and other mid-word periods (e.g. "Dr.", "3.14", "U.S.A"). | ||
| if is_no_space: | ||
| sentences = re.split(r"(?<=[。!?\u3002\uff01\uff1f])\s*", text) # noqa: RUF001 |
There was a problem hiding this comment.
| # Sentence-boundary split: keep the delimiter attached to the preceding sentence. | |
| # For no-space text (CJK etc.) use \s* because sentences run together without | |
| # whitespace. For all other text use \s+ to avoid splitting on abbreviations, | |
| # decimals, URLs, and other mid-word periods (e.g. "Dr.", "3.14", "U.S.A"). | |
| if is_no_space: | |
| sentences = re.split(r"(?<=[。!?\u3002\uff01\uff1f])\s*", text) # noqa: RUF001 | |
| # Sentence-boundary split: keep the delimiter attached to the preceding sentence. | |
| # For no-space text (CJK etc.) use `\s*` because sentences run together without | |
| # whitespace. For all other text use `\s+`. | |
| if is_no_space: | |
| # split on special sentence boundaries and spaces if present | |
| sentences = re.split(r"(?<=[\u3002\uff01\uff1f])\s*", text) |
| current_count = 0 | ||
|
|
||
| # If a single sentence is longer than max_words on its own, hard-split it | ||
| if unit_count > max_words: |
There was a problem hiding this comment.
seems like two terminologies are used here, unit (refers to both words and chars) and words (refers to the same unit meaning but is written as words)
it would be nice to make max_words follow the same terms and be max_units or something so it's easier to reason about the code.
| continue | ||
|
|
||
| # If adding this sentence would overflow the chunk, flush first | ||
| if current_count + unit_count > max_words and current_parts: |
There was a problem hiding this comment.
like this naming is good, current_count and current_parts are related to the sentences loop and are constantly used and recycled, so "current".
| inference_config = {k: v for k, v in self.config["inference"].items() | ||
| if k != "max_batch_size"} | ||
| max_batch_size = self.config["inference"].get("max_batch_size", 32) | ||
|
|
||
| results = list(self.translator.translate_iterable( | ||
| all_input_tokens, | ||
| max_batch_size=max_batch_size, | ||
| batch_type="tokens", | ||
| **self.config["inference"], | ||
| ) | ||
| **inference_config, |
There was a problem hiding this comment.
why remove max_batch_size and write it separately?
the same could be achieved by checking if the key exists or not and setting a default if not.
also, 32 is small, we should do 256 since batch_type="tokens" so it's 256 tokens at a time.
| text_size = len(cleaned) if is_no_space_source else len(cleaned.split()) | ||
| chunks = ( | ||
| self._chunk_text(cleaned, chunk_size, is_no_space=is_no_space_source) | ||
| if text_size > chunk_threshold |
There was a problem hiding this comment.
for spaced languages, here we check the word count to be 250 max, which is very far off from the token count which is supposed to be max 256.
each tokenizer is different but generally it's 3-4 characters for 1 token in english and it's less characters for 1 token in other languages like hindi or chinese.
we could do a character count check here and say max 600 chars maybe, for all the languages, but if you're feeling like it, we could use the tokenizer below to estimate the token count of the texts, which should be fast and check that to be more than the threshold to decide if we chunk or not.
Co-authored-by: Anupam Kumar <kyteinsky@gmail.com> Signed-off-by: Ravi Shankar Kumar <154051646+RSKKSOFFICIAL@users.noreply.github.com>
Co-authored-by: Anupam Kumar <kyteinsky@gmail.com> Signed-off-by: Ravi Shankar Kumar <154051646+RSKKSOFFICIAL@users.noreply.github.com>
Co-authored-by: Anupam Kumar <kyteinsky@gmail.com> Signed-off-by: Ravi Shankar Kumar <154051646+RSKKSOFFICIAL@users.noreply.github.com>
Co-authored-by: Anupam Kumar <kyteinsky@gmail.com> Signed-off-by: Ravi Shankar Kumar <154051646+RSKKSOFFICIAL@users.noreply.github.com>
Signed-off-by: RSKKSOFFICIAL <rsksofficial02@gmail.com>
No worries at all about the review taking some time. I really appreciate the feedback and suggestions from both (@marcelklehr @kyteinsky ) of you. 😊 I’ve addressed the latest comments and pushed the changes. I’ll definitely ask if I have any questions or if there’s anything I’d like to discuss further. I’m learning a lot from these reviews, not just how to make the code work, but how to write maintainable, clean, and well-integrated code that fits the Nextcloud ecosystem. Thanks again for your time and guidance! |
Signed-off-by: Ravi Shankar <142860126+RSKSOFFICIAL@users.noreply.github.com>
Signed-off-by: Ravi Shankar <142860126+RSKSOFFICIAL@users.noreply.github.com>
Signed-off-by: Ravi Shankar <142860126+RSKSOFFICIAL@users.noreply.github.com>
for more information, see https://pre-commit.ci
3b3e9a0 to
cbe1cbf
Compare
Signed-off-by: Ravi Shankar <142860126+RSKSOFFICIAL@users.noreply.github.com>
Fixes #71
What this does
Large input texts (>250 words) were being silently truncated by the model
because the output hit the decoding length limit mid-document. This adds a
chunking layer that splits the input into smaller pieces before translation,
then joins the results back into a single output.
Changes
lib/Service.py_chunk_text(): splits input at sentence boundaries into chunks ofmax 80 words. Hard-splits any single sentence that exceeds the limit.
_join_chunks(): joins translated chunks in document order. Uses anempty string separator for no-space languages (zh, ja, th, etc.) and a
single space for all others. Chunk order is always preserved regardless of
source/target script direction — each chunk is already translated correctly
by the model independently.
translate(): applies chunking when input exceeds the threshold,caps
max_decoding_lengthproportionally per chunk to prevent runawayrepetition loops, and enforces a minimum
repetition_penaltyper chunk.config.jsonchunkingsection with four configurable parameters:chunk_threshold(250): word count above which input is chunkedchunk_size(80): max words per chunkmin_repetition_penalty(1.5): lower bound for repetition penalty per chunk, prevents output loops on dense scripts like Devanagarimax_decoding_multiplier(3): output token cap as a multiple of input tokens per chunkTesting
Tested with 350+ word inputs across 7 language pairs:
All pairs now produce complete output covering the full input. Before this
change every pair was truncated at roughly Section 03/04 of a 5-section test
document.
RTL languages (Arabic, Persian) are handled correctly, chunks are always
joined in forward document order since each chunk is translated independently.