File size: 27,890 Bytes
024904f
 
 
 
 
 
 
 
 
60f9cf6
 
 
546002b
024904f
d7e74d5
 
024904f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3de7ef9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
024904f
88c98f1
20db579
 
024904f
 
d7e74d5
 
 
 
 
 
9e5107e
d7e74d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9e5107e
d7e74d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82ed427
9009e51
d7e74d5
9009e51
 
 
d7e74d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9e5107e
d7e74d5
9e5107e
d7e74d5
9009e51
d7e74d5
 
 
 
 
 
 
9e5107e
 
d7e74d5
 
 
 
 
 
9009e51
20ec6bd
 
9009e51
20ec6bd
 
 
9009e51
d7e74d5
9e5107e
 
d7e74d5
 
9e5107e
 
 
d7e74d5
 
 
9009e51
 
 
d7e74d5
9e5107e
d7e74d5
9009e51
 
9e5107e
9009e51
 
d7e74d5
 
 
 
9e5107e
 
d7e74d5
 
 
 
 
9009e51
9e5107e
9009e51
 
 
9e5107e
 
 
d7e74d5
 
 
 
 
 
 
 
82ed427
d7e74d5
82ed427
d7e74d5
82ed427
d7e74d5
82ed427
d7e74d5
82ed427
d7e74d5
82ed427
d7e74d5
82ed427
d7e74d5
82ed427
d7e74d5
82ed427
d7e74d5
82ed427
d7e74d5
82ed427
d7e74d5
82ed427
d7e74d5
82ed427
d7e74d5
82ed427
d7e74d5
82ed427
d7e74d5
82ed427
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d7e74d5
 
 
9e5107e
82ed427
d7e74d5
024904f
 
 
 
 
 
 
 
 
 
 
 
 
 
8aeaba0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
---
license: mit
language:
- en
- zh
pipeline_tag: token-classification
---
# bert-chunker-Chinese-2

[GitHub](https://github.com/jackfsuia/bert-chunker/tree/main/bcc2)


bert-chunker-Chinese-2 (中文分段器) is a text chunker based on BertForTokenClassification to predict the start token of chunks (for use in RAG, etc), and using a sliding window it cuts documents of any size into chunks. We see it as an alternative of [semantic chunker](https://github.com/FullStackRetrieval-com/RetrievalTutorials/blob/main/tutorials/LevelsOfTextSplitting/5_Levels_Of_Text_Splitting.ipynb), but specially, it not only works for the structured texts, but also the **unstructured and messy texts**. It is a new version of [bc-chinese](https://huggingface.co/tim1900/bert-chunker-chinese), for which we change our data labeling and train pipeline to make it more stable and useful.

Updates :
- 2025.5.12: an experimental script that **supports specifying the maximum tokens per chunk** is available now [below](#experimental).
## Usage
Run the following:

```python
# -*- coding: utf-8 -*-
import torch
from transformers import AutoTokenizer, BertForTokenClassification
import math

model_path = "tim1900/bert-chunker-Chinese-2"

tokenizer = AutoTokenizer.from_pretrained(
    model_path,
    padding_side="right",
    model_max_length=512,
    trust_remote_code=True,
)

device = "cpu"  # or 'cuda'

model = BertForTokenClassification.from_pretrained(
    model_path,
).to(device)

def chunk_text(model, text, tokenizer, prob_threshold=0.5):
    # slide context window chunking
    MAX_TOKENS = 512
    tokens = tokenizer(text, return_tensors="pt", truncation=False)
    input_ids = tokens["input_ids"]
    attention_mask = tokens["attention_mask"][:, 0:MAX_TOKENS]
    attention_mask = attention_mask.to(model.device)
    CLS = input_ids[:, 0].unsqueeze(0)
    SEP = input_ids[:, -1].unsqueeze(0)
    input_ids = input_ids[:, 1:-1]
    model.eval()
    split_str_poses = []
    token_pos = []
    windows_start = 0
    windows_end = 0
    logits_threshold = math.log(1 / prob_threshold - 1)
    print(f"Processing {input_ids.shape[1]} tokens...")
    while windows_end <= input_ids.shape[1]:
        windows_end = windows_start + MAX_TOKENS - 2

        ids = torch.cat((CLS, input_ids[:, windows_start:windows_end], SEP), 1)

        ids = ids.to(model.device)

        output = model(
            input_ids=ids,
            attention_mask=torch.ones(1, ids.shape[1], device=model.device),
        )
        logits = output["logits"][:, 1:-1, :]
        chunk_decision = logits[:, :, 1] > (logits[:, :, 0] - logits_threshold)
        greater_rows_indices = torch.where(chunk_decision)[1].tolist()

        # null or not
        if len(greater_rows_indices) > 0 and (
            not (greater_rows_indices[0] == 0 and len(greater_rows_indices) == 1)
        ):

            split_str_pos = [
                tokens.token_to_chars(sp + windows_start + 1).start
                for sp in greater_rows_indices
                if sp > 0
            ]
            token_pos += [
                sp + windows_start + 1 for sp in greater_rows_indices if sp > 0
            ]
            split_str_poses += split_str_pos

            windows_start = greater_rows_indices[-1] + windows_start

        else:

            windows_start = windows_end

    substrings = [
        text[i:j] for i, j in zip([0] + split_str_poses, split_str_poses + [len(text)])
    ]
    token_pos = [0] + token_pos
    return substrings, token_pos


# chunking
print("\n>>>>>>>>> Chunking...")
doc = r'''经典中式家常菜:红烧肉 详尽版食谱

红烧肉,作为中华美食殿堂中一颗璀璨的明珠,以其色泽红亮、肥而不腻、入口即化的独特风味,征服了无数食客的味蕾。它不仅仅是一道菜,更是一种情怀,是记忆中妈妈厨房里飘出的诱人香气,是团圆饭桌上最温暖的慰藉。要烹制出一盘完美的红烧肉,需要耐心、技巧以及对细节的专注。以下将为您详尽分解从选材到成品的每一个步骤,力求让您在家也能复刻出餐厅级别的美味。本食谱旨在深入剖析,故篇幅较长,以确保每个环节都清晰透彻。

一、 精选主料:奠定美味的基石

•   猪肉选择。

    ◦   首选带皮的五花肉。

    ◦   层次分明为佳。

    ◦   肥瘦相间是关键。

    ◦   厚度约三指左右。

    ◦   重量约一斤半为宜。

    ◦   确保猪皮完整无缺。

    ◦   新鲜肉品呈鲜红色。

    ◦   脂肪部分应洁白细腻。

•   预处理工作。

    ◦   用镊子拔除残留猪毛。

    ◦   将肉块置于冷水中浸泡。

    ◦   浸泡时间约三十分钟。

    ◦   中途可换水一至两次。

    ◦   目的是泡出部分血水。

    ◦   然后用刀刮洗猪皮表面。

    ◦   彻底清除污物和杂质。

    ◦   最后用流动水冲洗干净。

二、 准备辅料:调配灵魂之味

•   主要调味品。

    ◦   老抽:负责上色。

    ◦   用量需谨慎控制。

    ◦   过多会导致发黑发苦。

    ◦   生抽:提供咸鲜底味。

    ◦   与老抽比例约二比一。

    ◦   优质花雕酒是精髓。

    ◦   去腥增香效果显著。

    ◦   冰糖:首选黄冰糖。

    ◦   炒出的糖色更红亮。

    ◦   风味也比白糖醇和。

•   香辛料组合。

    ◦   生姜一大块,切片。

    ◦   大葱一根,切成长段。

    ◦   蒜头数瓣,轻轻拍松。

    ◦   八角两至三颗,增香。

    ◦   桂皮一小段,勿过多。

    ◦   香叶两片,增添风味。

    ◦   草果一颗,可拍裂开。

    ◦   干辣椒依个人口味添加。

•   其他基础材料。

    ◦   食用油适量,需耐高温。

    ◦   最好使用菜籽油或花生油。

    ◦   食盐少许,用于最后调味。

    ◦   因为酱油已有咸度。

    ◦   准备足量的开水备用。

    ◦   切记不可使用冷水。

    ◦   冷水会使肉质收缩变柴。

三、 精细加工:关键步骤解析

•   肉块改刀。

    ◦   将洗净的五花肉捞出。

    ◦   用厨房纸彻底吸干水分。

    ◦   这一步非常重要。

    ◦   能有效防止后续溅油。

    ◦   将肉切成三厘米见方块。

    ◦   大小尽量保持均匀一致。

    ◦   以确保受热和入味均匀。

    ◦   切面应能看到完美层次。

•   焯水去腥。

    ◦   冷水下锅,放入切好的肉。

    ◦   同时加入几片生姜。

    ◦   倒入一汤匙花雕酒。

    ◦   开大火煮沸,撇净浮沫。

    ◦   浮沫是血水和杂质所致。

    ◦   务必撇除干净直至汤清。

    ◦   焯水时间约五到八分钟。

    ◦   煮至肉块变色定型即可。

•   捞出与冲洗。

    ◦   用漏勺将肉块捞出。

    ◦   立即放入温水中冲洗。

    ◦   洗去表面残留的浮沫。

    ◦   注意水温不宜过低。

    ◦   再次用厨房纸吸干水分。

    ◦   防止入锅时油花四溅。

    ◦   此时肉块呈灰白色。

    ◦   经过焯水已无肉腥味。

四、 核心工艺:炒糖色与煸炒

•   炒制糖色。

    ◦   锅烧热,倒入少量底油。

    ◦   放入准备好的冰糖。

    ◦   开中小火慢慢搅动。

    ◦   观察冰糖融化的过程。

    ◦   先从固体变为液态。

    ◦   再从小泡转为密集大泡。

    ◦   当大泡逐渐回落消失时。

    ◦   糖液颜色开始加深。

•   观察颜色变化。

    ◦   从浅黄色变为枣红色。

    ◦   这个瞬间非常关键。

    ◦   枣红色时立即下入肉块。

    ◦   过早则甜腻,过晚则发苦。

    ◦   动作务必迅速而准确。

    ◦   糖色是红亮色泽的来源。

    ◦   也是风味层次的基础。

•   煸炒肉块。

    ◦   快速颠锅,使每块肉均匀裹上糖色。

    ◦   持续翻炒约三到五分钟。

    ◦   直到肉块表面微微焦黄。

    ◦   部分油脂被煸炒出来。

    ◦   这样吃起来肥而不腻。

    ◦   同时香味物质充分释放。

    ◦   煸出的猪油可倒出部分。

    ◦   留作炒青菜风味极佳。

五、 炖煮入味:时间与火候的艺术

•   加入调料。

    ◦   沿着锅边烹入花雕酒。

    ◦   瞬间激发出浓郁酒香。

    ◦   接着倒入适量生抽。

    ◦   再加入少许老抽上色。

    ◦   放入所有香料:姜、葱、蒜等。

    ◦   与肉块一起翻炒均匀。

    ◦   让酱香与肉香充分融合。

    ◦   翻炒约两分钟至香气扑鼻。

•   注入开水。

    ◦   务必一次性加足开水。

    ◦   水量要完全没过肉块。

    ◦   甚至可以略多一些。

    ◦   避免中途再次加水。

    ◦   大火烧开后转小火。

    ◦   盖上锅盖,慢火焖炖。

    ◦   这是“入口即化”的关键。

    ◦   时间至少需要一小时。

•   慢炖过程。

    ◦   保持汤面微沸即可。

    ◦   火候切忌过大过急。

    ◦   否则容易烧干且肉不烂。

    ◦   期间可偶尔开盖查看。

    ◦   用勺子轻轻推动一下。

    ◦   防止粘锅底的情况发生。

    ◦   但尽量不要频繁翻动。

    ◦   以免影响肉块的完整。

六、 收汁与装盘:成就最终美味

•   大火收汁。

    ◦   炖煮一小时后。

    ◦   用筷子戳一下瘦肉部分。

    ◦   若能轻松戳透即表示已软烂。

    ◦   此时根据汤汁咸度加盐。

    ◦   开大火,将汤汁收浓。

    ◦   用锅铲不停搅动。

    ◦   防止糊底,并让汤汁变稠。

    ◦   均匀包裹在每一块肉上。

•   收汁技巧。

    ◦   收到汤汁浓稠如蜜。

    ◦   油亮红润的汤汁紧裹肉块。

    ◦   锅中泛起密集的大泡。

    ◦   即可准备关火出锅。

    ◦   收汁程度依个人喜好。

    ◦   喜欢拌饭可多留些汤汁。

    ◦   整个过程需密切留意。

    ◦   最后阶段变化非常迅速。

•   最终成品。

    ◦   将红烧肉盛入预热好的盘中。

    ◦   可烫几棵小油菜围边。

    ◦   既点缀色彩,又解油腻。

    ◦   撒上少许葱花或香菜末。

    ◦   一道色香味俱全的红烧肉完成。

    ◦   肉质软糯,肥而不腻。

    ◦   咸中带甜,回味无穷。

    ◦   配上一碗白米饭是绝配。

七、 要点总结与升华

•   成功关键。

    ◦   选材是基础,务必新鲜。

    ◦   焯水步骤不可省略。

    ◦   炒糖色是技术核心。

    ◦   火候控制是成败关键。

    ◦   耐心慢炖是美味保证。

•   变化与创新。

    ◦   可加入土豆、鹌鹑蛋同烧。

    ◦   吸收肉汤,滋味更丰富。

    ◦   也可尝试用啤酒代替水。

    ◦   别有一番风味层次。

    ◦   但万变不离其宗。

    ◦   核心技法仍需掌握。

烹饪是一门需要实践的艺术,红烧肉更是如此。希望这份详尽的食谱能成为您厨房路上的得力助手,愿您能享受从准备到品尝的整个过程,与家人朋友分享这份由时间与匠心凝聚而成的温暖美味。每一次尝试都是一次经验的积累,祝您烹饪愉快,早日成就属于自己的招牌红烧肉!
'''
# Chunk the text. The prob_threshold should be between (0, 1). The lower it is, the more chunks will be generated.
# Therefore adjust it to your need, when prob_threshold is small like 0.000001, each token is one chunk,
# when it is set to 1, the whole text is one chunk.
chunks, token_pos = chunk_text(model, doc, tokenizer, prob_threshold=0.5)

# print chunks
for i, (c, t) in enumerate(zip(chunks, token_pos)):
    print(f"-----chunk: {i}----token_idx: {t}--------")
    print(c)
```
## Experimental
The following script supports specifying max tokens per chunk. Chunker will be forced to choose a best possible position from history to chunk when it is about to exceed the max_tokens_per_chunk and no token satisfy the prob_threshold. This script can be seen as a new experimental version of the scripts above.
```python
# -*- coding: utf-8 -*-
import torch
from transformers import AutoTokenizer, BertForTokenClassification
import math

model_path = "tim1900/bert-chunker-Chinese-2"

tokenizer = AutoTokenizer.from_pretrained(
    model_path,
    padding_side="right",
    model_max_length=512,
    trust_remote_code=True,
)

device = "cpu"  # or 'cuda'

model = BertForTokenClassification.from_pretrained(
    model_path,
).to(device)

def chunk_text_with_max_chunk_size(model, text, tokenizer, prob_threshold=0.5,max_tokens_per_chunk = 400):
    with torch.no_grad():
        
        # slide context window chunking
        MAX_TOKENS = 512
        tokens = tokenizer(text, return_tensors="pt", truncation=False)
        input_ids = tokens["input_ids"]
        attention_mask = tokens["attention_mask"][:, 0:MAX_TOKENS]
        attention_mask = attention_mask.to(model.device)
        CLS = input_ids[:, 0].unsqueeze(0)
        SEP = input_ids[:, -1].unsqueeze(0)
        input_ids = input_ids[:, 1:-1]
        model.eval()
        split_str_poses = []
        token_pos = []
        windows_start = 0
        windows_end = 0
        logits_threshold = math.log(1 / prob_threshold - 1)
        
        unchunk_tokens = 0
        backup_pos = None
        best_logits = torch.finfo(torch.float32).min 
        STEP = round(((MAX_TOKENS - 2)//2)*1.75 )
        print(f"Processing {input_ids.shape[1]} tokens...")
        # while windows_end <= input_ids.shape[1]:#记得改成windstart
        while windows_start < input_ids.shape[1]:#记得改成windstart    
            windows_end = windows_start + MAX_TOKENS - 2 
            ids = torch.cat((CLS, input_ids[:, windows_start:windows_end], SEP), 1)
            ids = ids.to(model.device)
            output = model(
                input_ids=ids,
                attention_mask=torch.ones(1, ids.shape[1], device=model.device),
            )
            logits = output["logits"][:, 1:-1, :]
            
            
            logit_diff = logits[:, :, 1] - logits[:, :, 0]
                    
                    
            chunk_decision = logit_diff > - logits_threshold
            greater_rows_indices = torch.where(chunk_decision)[1].tolist()

            # null or not
            if len(greater_rows_indices) > 0 and (
                not (greater_rows_indices[0] == 0 and len(greater_rows_indices) == 1)
            ):

                
                unchunk_tokens_this_window = greater_rows_indices[0] if greater_rows_indices[0]!=0 else greater_rows_indices[1]#exclude the fist index

                # manually chunk
                if unchunk_tokens + unchunk_tokens_this_window > max_tokens_per_chunk:
                    big_windows_end = max_tokens_per_chunk - unchunk_tokens
                    max_value, max_index= logit_diff[:,1:big_windows_end].max(),  logit_diff[:,1:big_windows_end].argmax() + 1
                    if best_logits < max_value:
                        backup_pos = windows_start + max_index
                    
                    windows_start = backup_pos
                    
                    
                    split_str_pos = [tokens.token_to_chars(backup_pos + 1).start]
                    split_str_poses = split_str_poses + split_str_pos
                    token_pos = token_pos + [backup_pos]
                    best_logits = torch.finfo(torch.float32).min
                    backup_pos = -1
                    unchunk_tokens = 0
                    
                # auto chunk    
                else:
                    
                    if len(greater_rows_indices) >= 2:
                        for gi, (gri0,gri1) in enumerate(zip(greater_rows_indices[:-1],greater_rows_indices[1:])):
                            
                            if gri1 - gri0 > max_tokens_per_chunk:
                                greater_rows_indices=greater_rows_indices[:gi+1]
                                break
                                
                    split_str_pos = [tokens.token_to_chars(sp + windows_start + 1).start for sp in greater_rows_indices if sp > 0]
                    split_str_poses = split_str_poses + split_str_pos
                    token_pos = token_pos+ [sp + windows_start for sp in greater_rows_indices if sp > 0]
                    
                    windows_start = greater_rows_indices[-1] + windows_start
                    best_logits = torch.finfo(torch.float32).min
                    backup_pos = -1
                    unchunk_tokens = 0

            else:

                # unchunk_tokens_this_window = min(windows_end - windows_start,STEP)
                unchunk_tokens_this_window = min(windows_start+STEP,input_ids.shape[1]) - windows_start

                # manually chunk
                if unchunk_tokens + unchunk_tokens_this_window > max_tokens_per_chunk:
                    big_windows_end =  max_tokens_per_chunk - unchunk_tokens
                    if logit_diff.shape[1] > 1:
                        
                        max_value, max_index= logit_diff[:,1:big_windows_end].max(),  logit_diff[:,1:big_windows_end].argmax() + 1
                        if best_logits < max_value:
                            backup_pos = windows_start + max_index
                        
                        
                    windows_start = backup_pos
                    split_str_pos = [tokens.token_to_chars(backup_pos + 1).start]
                    split_str_poses = split_str_poses + split_str_pos
                    token_pos = token_pos + [backup_pos]
                    best_logits = torch.finfo(torch.float32).min
                    backup_pos = -1
                    unchunk_tokens = 0
                else:
                # auto leave
                    if logit_diff.shape[1] > 1:
                        max_value, max_index= logit_diff[:,1:].max(),  logit_diff[:,1:].argmax() + 1
                        if best_logits < max_value:
                            best_logits = max_value
                            backup_pos = windows_start + max_index
                       
                    unchunk_tokens = unchunk_tokens + STEP
                    windows_start = windows_start + STEP

        substrings = [
            text[i:j] for i, j in zip([0] + split_str_poses, split_str_poses + [len(text)])
        ]
        token_pos = [0] + token_pos
    return substrings, token_pos
# chunking
print("\n>>>>>>>>> Chunking...")
doc = r'''经典中式家常菜:红烧肉 详尽版食谱

红烧肉,作为中华美食殿堂中一颗璀璨的明珠,以其色泽红亮、肥而不腻、入口即化的独特风味,征服了无数食客的味蕾。它不仅仅是一道菜,更是一种情怀,是记忆中妈妈厨房里飘出的诱人香气,是团圆饭桌上最温暖的慰藉。要烹制出一盘完美的红烧肉,需要耐心、技巧以及对细节的专注。以下将为您详尽分解从选材到成品的每一个步骤,力求让您在家也能复刻出餐厅级别的美味。本食谱旨在深入剖析,故篇幅较长,以确保每个环节都清晰透彻。

一、 精选主料:奠定美味的基石

•   猪肉选择。

    ◦   首选带皮的五花肉。

    ◦   层次分明为佳。

    ◦   肥瘦相间是关键。

    ◦   厚度约三指左右。

    ◦   重量约一斤半为宜。

    ◦   确保猪皮完整无缺。

    ◦   新鲜肉品呈鲜红色。

    ◦   脂肪部分应洁白细腻。

•   预处理工作。

    ◦   用镊子拔除残留猪毛。

    ◦   将肉块置于冷水中浸泡。

    ◦   浸泡时间约三十分钟。

    ◦   中途可换水一至两次。

    ◦   目的是泡出部分血水。

    ◦   然后用刀刮洗猪皮表面。

    ◦   彻底清除污物和杂质。

    ◦   最后用流动水冲洗干净。

二、 准备辅料:调配灵魂之味

•   主要调味品。

    ◦   老抽:负责上色。

    ◦   用量需谨慎控制。

    ◦   过多会导致发黑发苦。

    ◦   生抽:提供咸鲜底味。

    ◦   与老抽比例约二比一。

    ◦   优质花雕酒是精髓。

    ◦   去腥增香效果显著。

    ◦   冰糖:首选黄冰糖。

    ◦   炒出的糖色更红亮。

    ◦   风味也比白糖醇和。

•   香辛料组合。

    ◦   生姜一大块,切片。

    ◦   大葱一根,切成长段。

    ◦   蒜头数瓣,轻轻拍松。

    ◦   八角两至三颗,增香。

    ◦   桂皮一小段,勿过多。

    ◦   香叶两片,增添风味。

    ◦   草果一颗,可拍裂开。

    ◦   干辣椒依个人口味添加。

•   其他基础材料。

    ◦   食用油适量,需耐高温。

    ◦   最好使用菜籽油或花生油。

    ◦   食盐少许,用于最后调味。

    ◦   因为酱油已有咸度。

    ◦   准备足量的开水备用。

    ◦   切记不可使用冷水。

    ◦   冷水会使肉质收缩变柴。

三、 精细加工:关键步骤解析

•   肉块改刀。

    ◦   将洗净的五花肉捞出。

    ◦   用厨房纸彻底吸干水分。

    ◦   这一步非常重要。

    ◦   能有效防止后续溅油。

    ◦   将肉切成三厘米见方块。

    ◦   大小尽量保持均匀一致。

    ◦   以确保受热和入味均匀。

    ◦   切面应能看到完美层次。

•   焯水去腥。

    ◦   冷水下锅,放入切好的肉。

    ◦   同时加入几片生姜。

    ◦   倒入一汤匙花雕酒。

    ◦   开大火煮沸,撇净浮沫。

    ◦   浮沫是血水和杂质所致。

    ◦   务必撇除干净直至汤清。

    ◦   焯水时间约五到八分钟。

    ◦   煮至肉块变色定型即可。

•   捞出与冲洗。

    ◦   用漏勺将肉块捞出。

    ◦   立即放入温水中冲洗。

    ◦   洗去表面残留的浮沫。

    ◦   注意水温不宜过低。

    ◦   再次用厨房纸吸干水分。

    ◦   防止入锅时油花四溅。

    ◦   此时肉块呈灰白色。

    ◦   经过焯水已无肉腥味。

四、 核心工艺:炒糖色与煸炒

•   炒制糖色。

    ◦   锅烧热,倒入少量底油。

    ◦   放入准备好的冰糖。

    ◦   开中小火慢慢搅动。

    ◦   观察冰糖融化的过程。

    ◦   先从固体变为液态。

    ◦   再从小泡转为密集大泡。

    ◦   当大泡逐渐回落消失时。

    ◦   糖液颜色开始加深。

•   观察颜色变化。

    ◦   从浅黄色变为枣红色。

    ◦   这个瞬间非常关键。

    ◦   枣红色时立即下入肉块。

    ◦   过早则甜腻,过晚则发苦。

    ◦   动作务必迅速而准确。

    ◦   糖色是红亮色泽的来源。

    ◦   也是风味层次的基础。

•   煸炒肉块。

    ◦   快速颠锅,使每块肉均匀裹上糖色。

    ◦   持续翻炒约三到五分钟。

    ◦   直到肉块表面微微焦黄。

    ◦   部分油脂被煸炒出来。

    ◦   这样吃起来肥而不腻。

    ◦   同时香味物质充分释放。

    ◦   煸出的猪油可倒出部分。

    ◦   留作炒青菜风味极佳。

五、 炖煮入味:时间与火候的艺术

•   加入调料。

    ◦   沿着锅边烹入花雕酒。

    ◦   瞬间激发出浓郁酒香。

    ◦   接着倒入适量生抽。

    ◦   再加入少许老抽上色。

    ◦   放入所有香料:姜、葱、蒜等。

    ◦   与肉块一起翻炒均匀。

    ◦   让酱香与肉香充分融合。

    ◦   翻炒约两分钟至香气扑鼻。

•   注入开水。

    ◦   务必一次性加足开水。

    ◦   水量要完全没过肉块。

    ◦   甚至可以略多一些。

    ◦   避免中途再次加水。

    ◦   大火烧开后转小火。

    ◦   盖上锅盖,慢火焖炖。

    ◦   这是“入口即化”的关键。

    ◦   时间至少需要一小时。

•   慢炖过程。

    ◦   保持汤面微沸即可。

    ◦   火候切忌过大过急。

    ◦   否则容易烧干且肉不烂。

    ◦   期间可偶尔开盖查看。

    ◦   用勺子轻轻推动一下。

    ◦   防止粘锅底的情况发生。

    ◦   但尽量不要频繁翻动。

    ◦   以免影响肉块的完整。

六、 收汁与装盘:成就最终美味

•   大火收汁。

    ◦   炖煮一小时后。

    ◦   用筷子戳一下瘦肉部分。

    ◦   若能轻松戳透即表示已软烂。

    ◦   此时根据汤汁咸度加盐。

    ◦   开大火,将汤汁收浓。

    ◦   用锅铲不停搅动。

    ◦   防止糊底,并让汤汁变稠。

    ◦   均匀包裹在每一块肉上。

•   收汁技巧。

    ◦   收到汤汁浓稠如蜜。

    ◦   油亮红润的汤汁紧裹肉块。

    ◦   锅中泛起密集的大泡。

    ◦   即可准备关火出锅。

    ◦   收汁程度依个人喜好。

    ◦   喜欢拌饭可多留些汤汁。

    ◦   整个过程需密切留意。

    ◦   最后阶段变化非常迅速。

•   最终成品。

    ◦   将红烧肉盛入预热好的盘中。

    ◦   可烫几棵小油菜围边。

    ◦   既点缀色彩,又解油腻。

    ◦   撒上少许葱花或香菜末。

    ◦   一道色香味俱全的红烧肉完成。

    ◦   肉质软糯,肥而不腻。

    ◦   咸中带甜,回味无穷。

    ◦   配上一碗白米饭是绝配。

七、 要点总结与升华

•   成功关键。

    ◦   选材是基础,务必新鲜。

    ◦   焯水步骤不可省略。

    ◦   炒糖色是技术核心。

    ◦   火候控制是成败关键。

    ◦   耐心慢炖是美味保证。

•   变化与创新。

    ◦   可加入土豆、鹌鹑蛋同烧。

    ◦   吸收肉汤,滋味更丰富。

    ◦   也可尝试用啤酒代替水。

    ◦   别有一番风味层次。

    ◦   但万变不离其宗。

    ◦   核心技法仍需掌握。

烹饪是一门需要实践的艺术,红烧肉更是如此。希望这份详尽的食谱能成为您厨房路上的得力助手,愿您能享受从准备到品尝的整个过程,与家人朋友分享这份由时间与匠心凝聚而成的温暖美味。每一次尝试都是一次经验的积累,祝您烹饪愉快,早日成就属于自己的招牌红烧肉!。
'''
# Chunk the text. The prob_threshold should be between (0, 1). The lower it is, the more chunks will be generated.
# Therefore adjust it to your need, when prob_threshold is small like 0.000001, each token is one chunk,
# when it is set to 1, the whole text will be one chunk, and will be forced to choose a best possible position to chunk when it is about to exceed the max_tokens_per_chunk and no token satisfy the prob_threshold.
chunks, token_pos = chunk_text_with_max_chunk_size(model, doc, tokenizer, prob_threshold=0.5, max_tokens_per_chunk = 100)

# print chunks
for i, (c, t) in enumerate(zip(chunks, token_pos)):
    print(f"-----chunk: {i}----token_idx: {t}--------")
    print(c)
```
## Citation
```bibtex
@article{bert-chunker,
  title={bert-chunker: Efficient and Trained Chunking for Unstructured Documents}, 
  author={Yannan Luo},
  year={2024},
  url={https://github.com/jackfsuia/bert-chunker}
}
```
Base model is from [bge-small-zh-v1.5](https://huggingface.co/BAAI/bge-small-zh-v1.5).