File size: 10,283 Bytes
fa3bb0d
 
 
3b55258
fa3bb0d
 
 
 
 
 
 
3b55258
c92dbd3
fa3bb0d
cf62af8
fa3bb0d
 
82d808c
 
 
 
 
 
3b55258
 
 
 
99a671b
 
 
c92dbd3
 
 
 
 
 
 
 
 
99a671b
c92dbd3
 
99a671b
 
 
c92dbd3
99a671b
 
 
3b55258
 
 
fa3bb0d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3b55258
c92dbd3
3b55258
 
fa3bb0d
 
 
 
82d808c
 
fa3bb0d
 
 
82d808c
fa3bb0d
82d808c
fa3bb0d
 
82d808c
 
fa3bb0d
3b55258
 
c92dbd3
 
5f40cee
 
ac8a7e2
c92dbd3
 
 
 
 
fa3bb0d
82d808c
c92dbd3
 
 
 
 
fa3bb0d
c92dbd3
fa3bb0d
c92dbd3
fa3bb0d
c92dbd3
 
fa3bb0d
 
c92dbd3
 
82d808c
c92dbd3
82d808c
 
 
 
 
 
 
 
 
 
 
c92dbd3
82d808c
 
 
 
 
 
c92dbd3
82d808c
 
c92dbd3
82d808c
fa3bb0d
c92dbd3
82d808c
 
 
 
c92dbd3
82d808c
 
 
 
 
 
 
 
 
 
c92dbd3
82d808c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3b55258
 
c92dbd3
82d808c
 
3b55258
 
c92dbd3
82d808c
 
c92dbd3
 
 
 
 
82d808c
 
 
 
 
c92dbd3
82d808c
c92dbd3
82d808c
c92dbd3
82d808c
c92dbd3
82d808c
 
e13c1ed
 
 
 
fa3bb0d
3b55258
fa3bb0d
82d808c
3b55258
e13c1ed
 
c92dbd3
3b55258
fa3bb0d
e13c1ed
c92dbd3
e13c1ed
fa3bb0d
e13c1ed
fa3bb0d
 
 
 
 
 
 
 
 
 
e13c1ed
fa3bb0d
 
e13c1ed
 
82d808c
 
fa3bb0d
 
 
 
e13c1ed
 
 
 
 
5f40cee
 
 
ac8a7e2
 
 
3b55258
e13c1ed
 
 
fa3bb0d
e13c1ed
fa3bb0d
e13c1ed
fa3bb0d
 
 
 
 
82d808c
fa3bb0d
 
 
 
 
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
"""
Hunter Omega — Live NIAH Demo
One-click needle in a haystack test from 8K to 12M tokens.
Uses real Wikipedia content as filler. Shows needle BEFORE querying.
"""
import gradio as gr
import requests
import random
import string
import time
import os
import re
import json

API_URL = os.environ.get("HUNTER_API")
MODEL = "hunter-omega-12m-fast"

SIZE_MAP = {
    "8K": 8000, "32K": 32000, "64K": 64000, "128K": 128000,
    "256K": 256000, "512K": 512000, "1M": 1000000,
    "1.5M": 1500000, "3M": 3000000, "6M": 6000000, "12M": 12000000,
}

WIKI_CACHE = []

def load_wikipedia():
    global WIKI_CACHE
    try:
        with open("wiki_cache.json") as f:
            WIKI_CACHE = json.load(f)
        print(f"Loaded {len(WIKI_CACHE)} Wikipedia articles")
    except:
        # Fallback: download
        articles = [
            "Solar_System", "Roman_Empire", "Photosynthesis", "Industrial_Revolution",
            "Quantum_mechanics", "French_Revolution", "DNA", "Climate_change",
            "Ancient_Egypt", "Theory_of_relativity", "Renaissance", "American_Civil_War",
        ]
        for title in articles:
            try:
                r = requests.get(f"https://en.wikipedia.org/api/rest_v1/page/html/{title}",
                                headers={"User-Agent": "Demo/1.0"}, timeout=10)
                if r.status_code == 200:
                    text = re.sub(r'<[^>]+>', ' ', r.text)
                    text = re.sub(r'\s+', ' ', text).strip()
                    WIKI_CACHE.append(' '.join(text.split()[:5000]))
            except:
                pass
        print(f"Downloaded {len(WIKI_CACHE)} articles")

load_wikipedia()


def generate_needle():
    code = ''.join(random.choices(string.ascii_uppercase, k=4))
    nums = ''.join(random.choices(string.digits, k=4))
    word = random.choice(["ALPHA", "BRAVO", "DELTA", "ECHO", "FOXTROT", "GOLF", "HOTEL",
                          "INDIA", "JULIET", "KILO", "LIMA", "MIKE", "NOVEMBER", "OSCAR",
                          "PAPA", "QUEBEC", "ROMEO", "SIERRA", "TANGO", "UNIFORM", "VICTOR"])
    return f"{code}-{nums}-{word}"


def generate_haystack(target_tokens):
    words = []
    target_words = target_tokens * 3 // 4
    i = 0
    while len(words) < target_words:
        if WIKI_CACHE:
            words.extend(WIKI_CACHE[i % len(WIKI_CACHE)].split())
        else:
            words.extend("The quick brown fox jumps over the lazy dog.".split())
        i += 1
    return words[:target_words]


def run_test(context_size):
    tokens = SIZE_MAP.get(context_size, 128000)
    needle_code = generate_needle()
    needle = f"The classified authorization code for Operation Starfall is {needle_code}."
    query = "What is the classified authorization code for Operation Starfall? State ONLY the code."
    depth = random.randint(10, 90) if tokens <= 1000000 else random.randint(55, 95)

    words = generate_haystack(tokens)
    insert_pos = int(len(words) * depth / 100)
    words.insert(insert_pos, needle)
    actual_words = len(words)
    actual_tokens = actual_words * 4 // 3

    wiki_count = len(WIKI_CACHE)

    # Estimate time for user
    time_estimates = {
        8000: "2-10s", 32000: "2-10s", 64000: "3-10s", 128000: "5-15s",
        256000: "15-30s", 512000: "30-60s", 1000000: "1-2 min",
        1500000: "2-3 min", 3000000: "4-6 min", 6000000: "8-12 min", 12000000: "20-30 min",
    }
    est = time_estimates.get(tokens, "unknown")

    # STEP 1: Show needle before querying
    setup = f"""## Step 1: Test Generated

**Context size:** {actual_tokens:,} tokens (~{actual_words:,} words)
**Filler:** Real Wikipedia articles ({wiki_count} articles including Solar System, Roman Empire, Quantum Mechanics, DNA, Climate Change, etc.)
**Needle:** `{needle_code}`
**Full sentence:** "{needle}"
**Position:** {depth}% depth (word {insert_pos:,} of {actual_words:,})
**Estimated time:** {est}

The passphrase above was just generated randomly. It is now hidden in {actual_tokens:,} tokens of Wikipedia text.

**Do not refresh your browser.** The result will appear below when complete.

---
## Step 2: """

    if tokens <= 1000000:
        # Native attention
        yield setup + "Querying model (native attention)...", "", "QUERYING...", ""

        haystack = " ".join(words)
        t0 = time.time()
        try:
            r = requests.post(API_URL, json={
                "model": MODEL,
                "messages": [{"role": "user", "content": haystack + "\n\n" + query}],
                "max_tokens": 50,
                "temperature": 0,
            }, timeout=300)
            elapsed = time.time() - t0

            if r.status_code != 200:
                yield setup + f"Server returned {r.status_code}. May be busy, please retry.", "", "ERROR", ""
                return

            answer = r.json()["choices"][0]["message"]["content"]
            found = needle_code.lower() in answer.lower()

        except requests.exceptions.Timeout:
            yield setup + "Timed out. Server may be busy. Please retry.", "", "TIMEOUT", ""
            return
        except Exception:
            yield setup + "Connection error. Please retry.", "", "ERROR", ""
            return
    else:
        # Extended context
        doc = " ".join(words)
        user_id = f"demo_{random.randint(100000, 999999)}"
        api_base = API_URL.replace("/v1/chat/completions", "")

        yield setup + f"Indexing {actual_tokens:,} tokens (this will take a few minutes)...", "", "INDEXING...", ""

        t0 = time.time()
        try:
            r = requests.post(f"{api_base}/v1/documents", json={
                "user_id": user_id, "text": doc,
            }, timeout=1800)
            index_time = time.time() - t0
            info = r.json()
            chunks = info.get("overflow_chunks", 0)

            yield setup + f"Indexed {chunks} chunks in {index_time:.0f}s. Now querying...", "", "QUERYING...", f"Index: {index_time:.0f}s"

            r = requests.post(API_URL, json={
                "model": MODEL,
                "messages": [{"role": "user", "content": query}],
                "max_tokens": 50,
                "temperature": 0,
                "user": user_id,
            }, timeout=600)
            elapsed = time.time() - t0
            answer = r.json()["choices"][0]["message"]["content"]
            found = needle_code.lower() in answer.lower()

            requests.delete(f"{api_base}/v1/documents/{user_id}", timeout=10)

        except requests.exceptions.Timeout:
            try: requests.delete(f"{api_base}/v1/documents/{user_id}", timeout=10)
            except: pass
            yield setup + f"Timed out after {time.time()-t0:.0f}s. Large documents take time. Please retry.", "", "TIMEOUT", f"{time.time()-t0:.0f}s"
            return
        except Exception:
            try: requests.delete(f"{api_base}/v1/documents/{user_id}", timeout=10)
            except: pass
            yield setup + "Connection error.", "", "ERROR", ""
            return

    # STEP 3: Result
    result = f"""Complete!

---
## Step 3: Result {"✅ NEEDLE FOUND" if found else "❌ NOT FOUND"}

**Expected:** `{needle_code}`
**Model returned:** `{answer.strip()}`
**Match:** {"YES" if found else "NO"}
**Time:** {elapsed:.1f}s
**Method:** {"Native full attention" if tokens <= 1000000 else "Extended context engine"}

{"The model correctly found the randomly generated passphrase in " + f"{actual_tokens:,} tokens of real Wikipedia content." if found else "The model did not find the needle."}

*Click 'Find the Needle' again to run another test with a fresh random passphrase.*"""

    yield setup + result, "", "FOUND" if found else "MISSED", f"{elapsed:.1f}s"


with gr.Blocks(title="Hunter Omega — Live NIAH Demo", css="""
    .gradio-container { max-width: 900px !important; }
    footer { display: none !important; }
""") as demo:
    gr.Markdown("""
# Hunter Omega — Needle in a Haystack (Live Demo)

**How this works:**
1. Pick a context length (8K to 12M tokens)
2. A **random passphrase** is generated and hidden at a **random depth** in **real Wikipedia articles**
3. You see the passphrase and its position **before** the model runs
4. The model searches and retrieves it
5. You verify the match

Fresh random passphrase every time. Real Wikipedia content, not repetitive filler text.

**8K to 1M:** Native full attention &nbsp;|&nbsp; **1.5M to 12M:** Extended effective context

> **Do not refresh your browser during a test.** Results appear in place when complete. Large contexts (6M+) can take several minutes.
    """)

    with gr.Row():
        context_dropdown = gr.Dropdown(
            choices=["8K", "32K", "64K", "128K", "256K", "512K", "1M", "1.5M", "3M", "6M", "12M"],
            value="128K",
            label="Context Length",
        )
        run_btn = gr.Button("Find the Needle", variant="primary", size="lg")

    output = gr.Markdown(label="Test Progress", value="Select a context length above and click **Find the Needle** to begin.")

    with gr.Row():
        status_box = gr.Textbox(label="Status", interactive=False, scale=1)
        time_box = gr.Textbox(label="Time", interactive=False, scale=1)

    hidden = gr.Textbox(visible=False)

    gr.Markdown("""
---

<details>
<summary><b>Estimated times and details</b></summary>

| Size | Method | Time (no queue) |
|:----:|:------:|:---------------:|
| 8K - 128K | Native attention | 2-15s |
| 256K - 512K | Native attention | 15-60s |
| 1M | Native attention | 1-2 min |
| 1.5M - 3M | Extended context | 2-6 min |
| 6M | Extended context | 8-12 min |
| 12M | Extended context | 20-30 min |

If multiple users are testing at the same time, requests queue. The model processes large-context requests one at a time.

**Filler:** 16 real Wikipedia articles (Solar System, Roman Empire, Quantum Mechanics, French Revolution, DNA, Climate Change, Ancient Egypt, Industrial Revolution, and more).

</details>

[Full benchmarks and methodology](https://github.com/SovNodeAI/hunter-omega-benchmarks) &nbsp;|&nbsp; Running on a single GPU cluster
    """)

    run_btn.click(
        fn=run_test,
        inputs=[context_dropdown],
        outputs=[output, hidden, status_box, time_box],
    )


if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860)