File size: 5,423 Bytes
96e003f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0d50b9b
96e003f
 
 
 
 
 
 
 
 
 
 
0d50b9b
96e003f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
from threading import Thread
import re

MODEL_PATH = "VDrontV2-0.1b-Title"
TEMPERATURE = 0.4
MAX_NEW_TOKENS = 64
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"

STOP_TOKENS = ["<|endoftext|>", "(end_title)", "(title)", "\n\n\n"]
STOP_PATTERNS = [
    r"\(end_title\)",
    r"\(title\)",
    r"<\|endoftext\|>",
]


def load_model_and_tokenizer(model_path):
    tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=False)
    model = AutoModelForCausalLM.from_pretrained(
        model_path,
        torch_dtype=torch.float16 if DEVICE == "cuda" else torch.float32,
        device_map="auto",
        trust_remote_code=False
    )
    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token

    if tokenizer.eos_token is None:
        tokenizer.eos_token = "<|endoftext|>"
        tokenizer.eos_token_id = tokenizer.convert_tokens_to_ids("<|endoftext|>")

    return model, tokenizer


def should_stop(text, accumulated_text=""):
    full_text = accumulated_text + text

    for stop_token in STOP_TOKENS:
        if stop_token in full_text:
            return True, full_text.split(stop_token)[0]

    if len(full_text) > 100:
        last_part = full_text[-50:]
        if len(set(last_part.split())) <= 3:
            return True, full_text

    if len(full_text) > 60:
        for i in range(10, 30):
            pattern = full_text[-i:]
            if full_text.count(pattern) > 3:
                return True, full_text

    return False, full_text


def generate_stream(model, tokenizer, prompt, temperature=0.4, max_new_tokens=256):
    inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=2048)
    inputs = {k: v.to(model.device) for k, v in inputs.items()}

    streamer = TextIteratorStreamer(
        tokenizer,
        skip_prompt=True,
        skip_special_tokens=False,
        timeout=30.0
    )

    generation_kwargs = dict(
        **inputs,
        max_new_tokens=max_new_tokens,
        temperature=temperature,
        do_sample=True,
        top_p=0.96,
        repetition_penalty=1.15,
        pad_token_id=tokenizer.pad_token_id,
        eos_token_id=tokenizer.eos_token_id,
        streamer=streamer,
    )

    thread = Thread(target=model.generate, kwargs=generation_kwargs)
    thread.start()

    accumulated_text = ""

    try:
        for new_text in streamer:
            if new_text:
                accumulated_text += new_text

                stop, clean_text = should_stop(new_text,
                                               accumulated_text[:-len(new_text)] if len(accumulated_text) > len(
                                                   new_text) else "")

                if stop:
                    if clean_text and len(clean_text) > len(accumulated_text) - len(new_text):
                        yield clean_text
                    break

                partial_stop = False
                for stop_token in STOP_TOKENS:
                    for i in range(1, len(stop_token)):
                        if accumulated_text.endswith(stop_token[:i]):
                            partial_stop = True
                            break
                    if partial_stop:
                        break

                if not partial_stop:
                    yield new_text

                if len(accumulated_text) > 50 and len(accumulated_text.split()) > 10:
                    if any(pattern in accumulated_text for pattern in ["(end_title)", "(title)"]):
                        break
    except Exception as e:
        print(f"\n[Stream error: {e}]")

    thread.join(timeout=5)


def text_continuation(model, tokenizer, temperature):
    print(f"Instruct Mode (temp={temperature})")
    print("Enter your text and the model will continue it.")
    print("Commands: 'exit' or 'quit' — exit.")
    print(f"Stop tokens: {', '.join(STOP_TOKENS)}")
    print("=" * 60)

    while True:
        try:
            user_input = input("\nUser: ").strip()
        except (KeyboardInterrupt, EOFError):
            print("\nGoodbye!")
            break

        if user_input.lower() in ["exit", "quit"]:
            print("Goodbye!")
            break

        if not user_input:
            continue

        print("Title: ", end="", flush=True)

        formatted_prompt = f"(user){user_input}(title)"

        response_text = ""
        for token in generate_stream(model, tokenizer, formatted_prompt, temperature=temperature,
                                     max_new_tokens=MAX_NEW_TOKENS):
            print(token, end="", flush=True)
            response_text += token

            if len(response_text.split()) > 50:
                break

        print()

        if response_text:
            print(f"[Generated {len(response_text.split())} words]")


if __name__ == "__main__":
    print(f"Loading model from {MODEL_PATH}...")
    model, tokenizer = load_model_and_tokenizer(MODEL_PATH)
    print(f"Model loaded. Device: {DEVICE}")
    print(f"Tokenizer eos_token: '{tokenizer.eos_token}'")
    print(f"Tokenizer eos_token_id: {tokenizer.eos_token_id}")

    text_continuation(model, tokenizer, temperature=TEMPERATURE)