@@ -297,6 +297,9 @@ int tq_generate(tq_model_t* model, tq_tokenizer_t* tokenizer,
297297 2 , /* LLaMA 2 </s> */
298298 106 , /* Gemma4 <end_of_turn> */
299299 128001 , /* LLaMA 3 <|end_of_text|> */
300+ 128006 , /* LLaMA 3 <|start_header_id|> (new turn = stop) */
301+ 128007 , /* LLaMA 3 <|end_header_id|> */
302+ 128008 , /* LLaMA 3 <|start_of_role|> */
300303 128009 , /* LLaMA 3 <|eot_id|> */
301304 248044 , /* Qwen <|endoftext|> */
302305 248046 , /* Qwen <|im_end|> */
@@ -318,14 +321,48 @@ int tq_generate(tq_model_t* model, tq_tokenizer_t* tokenizer,
318321
319322 /* Skip special/thinking tokens that shouldn't appear in output.
320323 * Qwen3.5: <think>...</think>
321- * Gemma 4: thought, <channel|>, <tool|>, <mask>, <unused*> */
324+ * Gemma 4: thought, <channel|>, <tool|>, <mask>, <unused*>
325+ * LLaMA 3: <|start_header_id|>, <|reserved_special_token_*|> */
326+ int should_stop = 0 ;
322327 if (piece ) {
323328 if (strstr (piece , "<think>" ) || strstr (piece , "</think>" ) ||
324- strstr (piece , "thought " ) || strstr (piece , "<channel |>" ) ||
325- strstr (piece , "<tool|>" ) || strstr ( piece , "< mask>" ) ||
329+ strstr (piece , "<channel|> " ) || strstr (piece , "<tool |>" ) ||
330+ strstr (piece , "<mask>" ) ||
326331 strstr (piece , "<unused" ) || strstr (piece , "<|think" )) {
327332 piece = "" ;
328333 }
334+ /* Gemma 4 "thought" token: only filter if it's the EXACT piece
335+ * (not a substring of normal text like "thoughtful") */
336+ if (piece [0 ] != '\0' && strcmp (piece , "thought" ) == 0 ) {
337+ piece = "" ;
338+ }
339+ /* Stop generation on turn-boundary tokens (LLaMA 3 / Qwen only).
340+ * Gemma uses token ID-based EOS (106), not text-based detection. */
341+ if (strstr (piece , "<|start_header_id|>" ) ||
342+ strstr (piece , "<|eot_id|>" ) ||
343+ strstr (piece , "<|im_end|>" )) {
344+ should_stop = 1 ;
345+ piece = "" ;
346+ }
347+ /* Filter reserved special tokens */
348+ if (strstr (piece , "<|reserved_special_token" ) ||
349+ strstr (piece , "<1st>" ) || strstr (piece , "<2nd>" ) || strstr (piece , "<3rd>" )) {
350+ piece = "" ;
351+ }
352+ }
353+ if (should_stop ) break ;
354+
355+ /* Also check accumulated output for turn markers that span multiple tokens */
356+ if (output && output_pos > 5 ) {
357+ const char * tail = output + (output_pos > 20 ? output_pos - 20 : 0 );
358+ if (strstr (tail , "<|start_header" ) || strstr (tail , "<|eot_id" ) ||
359+ strstr (tail , "<end_of_turn" ) || strstr (tail , "<|im_end" )) {
360+ /* Trim the marker from output */
361+ char * marker = strstr (output + (output_pos > 30 ? output_pos - 30 : 0 ), "<|" );
362+ if (!marker ) marker = strstr (output + (output_pos > 30 ? output_pos - 30 : 0 ), "<end" );
363+ if (marker ) { * marker = '\0' ; output_pos = (int )(marker - output ); }
364+ break ;
365+ }
329366 }
330367
331368 int piece_len = (int )strlen (piece );
0 commit comments