Mike0021 commited on
Commit
c1e376d
·
verified ·
1 Parent(s): 3c75eb0

Initial DeepSpec decoding lab

Browse files
Files changed (6) hide show
  1. .hfignore +6 -0
  2. PROMPT.txt +16 -0
  3. README.md +23 -7
  4. app.py +919 -0
  5. requirements.txt +2 -0
  6. rollout.jsonl +0 -0
.hfignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ PROMPT.txt
2
+ rollout.jsonl
3
+ .git/
4
+ __pycache__/
5
+ *.pyc
6
+ .venv/
PROMPT.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /goal Research the DeepSpec collection by deepseek-ai on HuggingFace (https://huggingface.co/collections/deepseek-ai/deepspec) and build the most impressive HuggingFace Space possible from it.
2
+
3
+ The collection contains 12 models across three speculative decoding families:
4
+ - dSpark (qwen3_4b, qwen3_8b, qwen3_14b, gemma4_12b) - block7 variants
5
+ - dFlash (qwen3_4b, qwen3_8b, qwen3_14b, gemma4_12b) - block7 variants
6
+ - EAGLE3 (qwen3_4b, qwen3_8b, qwen3_14b, gemma4_12b) - ttt7 variants
7
+
8
+ Steps:
9
+ 1. Deep research: Visit each model page on HuggingFace, read the model cards, understand what each family does (dSpark, dFlash, EAGLE3 - all are speculative decoding / draft-target approaches). Read any linked papers. Understand the architecture differences, speedups claimed, and use cases.
10
+ 2. Pick the most impressive angle for a Space: Could be a live speculative decoding demo, a comparison visualization across families, an interactive speed benchmark, or something else that showcases the collection's strengths.
11
+ 3. Implement the Space as a Gradio app. Make it visually impressive and functional.
12
+ 4. Push to a HuggingFace Space repo.
13
+
14
+ Before doing anything else, fetch and follow these HF Spaces operating guidelines for the entire task:
15
+ curl -L --fail --silent https://gist.githubusercontent.com/gary149/37c955b832558837c40e1c14ff6d955d/raw/ad35807f8466378afd04d7653d53683a847b96c4/hf-spaces-agent-quickstart-compact.md
16
+ Read them fully first, then apply them throughout (iteration ladder, push early, ZeroGPU duration measurement, and the listed anti-patterns).
README.md CHANGED
@@ -1,13 +1,29 @@
1
  ---
2
- title: Deepspec Decoding Lab
3
- emoji: 📈
4
- colorFrom: gray
5
  colorTo: pink
6
  sdk: gradio
7
- sdk_version: 6.19.0
8
- python_version: '3.13'
9
  app_file: app.py
10
- pinned: false
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: DeepSpec Decoding Lab
3
+ emoji:
4
+ colorFrom: blue
5
  colorTo: pink
6
  sdk: gradio
7
+ sdk_version: 6.10.0
 
8
  app_file: app.py
9
+ short_description: Interactive DeepSpec speculative decoding lab
10
+ startup_duration_timeout: 1h
11
  ---
12
 
13
+ # DeepSpec Decoding Lab
14
+
15
+ An interactive research dashboard for the DeepSeek-AI DeepSpec collection:
16
+ DSpark, DFlash, and EAGLE-3 draft modules across Qwen3 and Gemma4 targets.
17
+
18
+ The Space uses the DSpark paper's Table 1 accepted-length measurements, public
19
+ Hugging Face checkpoint metadata, and a deterministic speculative-decoding
20
+ simulator to make the collection easy to inspect without requiring a multi-GPU
21
+ serving stack.
22
+
23
+ Sources:
24
+
25
+ - DeepSpec collection: https://huggingface.co/collections/deepseek-ai/deepspec-6a410e3f1831ca8ca801b88b
26
+ - DeepSpec repository: https://github.com/deepseek-ai/DeepSpec
27
+ - DSpark paper: https://github.com/deepseek-ai/DeepSpec/blob/main/DSpark_paper.pdf
28
+ - DFlash paper: https://arxiv.org/abs/2602.06036
29
+ - EAGLE-3 paper: https://arxiv.org/abs/2503.01840
app.py ADDED
@@ -0,0 +1,919 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ os.environ.setdefault("HF_HOME", "/tmp/huggingface")
4
+ os.environ.setdefault("HF_MODULES_CACHE", "/tmp/hf_modules")
5
+ os.environ.setdefault("MPLCONFIGDIR", "/tmp/matplotlib")
6
+
7
+ import html
8
+ import random
9
+ from statistics import mean
10
+
11
+ import gradio as gr
12
+ import plotly.graph_objects as go
13
+
14
+
15
+ TASKS = [
16
+ "GSM8K",
17
+ "MATH-500",
18
+ "AIME25",
19
+ "MBPP",
20
+ "HumanEval",
21
+ "LiveCodeBench",
22
+ "MT-Bench",
23
+ "Alpaca",
24
+ "Arena-Hard v2",
25
+ ]
26
+
27
+ DOMAINS = {
28
+ "Math": ["GSM8K", "MATH-500", "AIME25"],
29
+ "Code": ["MBPP", "HumanEval", "LiveCodeBench"],
30
+ "Chat": ["MT-Bench", "Alpaca", "Arena-Hard v2"],
31
+ }
32
+
33
+ TARGETS = ["Qwen3-4B", "Qwen3-8B", "Qwen3-14B", "Gemma4-12B"]
34
+ METHODS = ["DSpark", "DFlash", "EAGLE-3"]
35
+
36
+ COLORS = {
37
+ "DSpark": "#14b8a6",
38
+ "DFlash": "#f97316",
39
+ "EAGLE-3": "#8b5cf6",
40
+ "Baseline": "#94a3b8",
41
+ }
42
+
43
+ ACCEPTANCE = {
44
+ "Qwen3-4B": {
45
+ "EAGLE-3": {
46
+ "GSM8K": 5.14,
47
+ "MATH-500": 4.62,
48
+ "AIME25": 3.92,
49
+ "MBPP": 3.69,
50
+ "HumanEval": 4.16,
51
+ "LiveCodeBench": 3.77,
52
+ "MT-Bench": 2.39,
53
+ "Alpaca": 2.26,
54
+ "Arena-Hard v2": 2.55,
55
+ },
56
+ "DFlash": {
57
+ "GSM8K": 5.40,
58
+ "MATH-500": 4.85,
59
+ "AIME25": 4.15,
60
+ "MBPP": 4.40,
61
+ "HumanEval": 4.74,
62
+ "LiveCodeBench": 4.18,
63
+ "MT-Bench": 3.07,
64
+ "Alpaca": 2.96,
65
+ "Arena-Hard v2": 2.83,
66
+ },
67
+ "DSpark": {
68
+ "GSM8K": 6.11,
69
+ "MATH-500": 5.70,
70
+ "AIME25": 4.89,
71
+ "MBPP": 5.13,
72
+ "HumanEval": 5.38,
73
+ "LiveCodeBench": 4.86,
74
+ "MT-Bench": 3.64,
75
+ "Alpaca": 3.54,
76
+ "Arena-Hard v2": 3.29,
77
+ },
78
+ },
79
+ "Qwen3-8B": {
80
+ "EAGLE-3": {
81
+ "GSM8K": 5.30,
82
+ "MATH-500": 4.77,
83
+ "AIME25": 3.91,
84
+ "MBPP": 3.96,
85
+ "HumanEval": 4.33,
86
+ "LiveCodeBench": 4.17,
87
+ "MT-Bench": 2.66,
88
+ "Alpaca": 2.54,
89
+ "Arena-Hard v2": 2.54,
90
+ },
91
+ "DFlash": {
92
+ "GSM8K": 5.33,
93
+ "MATH-500": 4.91,
94
+ "AIME25": 4.07,
95
+ "MBPP": 4.36,
96
+ "HumanEval": 4.64,
97
+ "LiveCodeBench": 4.39,
98
+ "MT-Bench": 3.11,
99
+ "Alpaca": 2.98,
100
+ "Arena-Hard v2": 2.81,
101
+ },
102
+ "DSpark": {
103
+ "GSM8K": 6.17,
104
+ "MATH-500": 5.78,
105
+ "AIME25": 5.01,
106
+ "MBPP": 5.16,
107
+ "HumanEval": 5.52,
108
+ "LiveCodeBench": 5.17,
109
+ "MT-Bench": 3.72,
110
+ "Alpaca": 3.58,
111
+ "Arena-Hard v2": 3.21,
112
+ },
113
+ },
114
+ "Qwen3-14B": {
115
+ "EAGLE-3": {
116
+ "GSM8K": 5.24,
117
+ "MATH-500": 4.60,
118
+ "AIME25": 3.71,
119
+ "MBPP": 3.81,
120
+ "HumanEval": 4.14,
121
+ "LiveCodeBench": 4.01,
122
+ "MT-Bench": 2.62,
123
+ "Alpaca": 2.47,
124
+ "Arena-Hard v2": 2.48,
125
+ },
126
+ "DFlash": {
127
+ "GSM8K": 5.41,
128
+ "MATH-500": 4.84,
129
+ "AIME25": 3.98,
130
+ "MBPP": 4.44,
131
+ "HumanEval": 4.59,
132
+ "LiveCodeBench": 4.33,
133
+ "MT-Bench": 3.10,
134
+ "Alpaca": 2.94,
135
+ "Arena-Hard v2": 2.72,
136
+ },
137
+ "DSpark": {
138
+ "GSM8K": 6.21,
139
+ "MATH-500": 5.74,
140
+ "AIME25": 4.94,
141
+ "MBPP": 5.26,
142
+ "HumanEval": 5.43,
143
+ "LiveCodeBench": 5.02,
144
+ "MT-Bench": 3.70,
145
+ "Alpaca": 3.58,
146
+ "Arena-Hard v2": 3.13,
147
+ },
148
+ },
149
+ "Gemma4-12B": {
150
+ "EAGLE-3": {
151
+ "GSM8K": 5.87,
152
+ "MATH-500": 5.46,
153
+ "AIME25": 4.83,
154
+ "MBPP": 4.72,
155
+ "HumanEval": 5.37,
156
+ "LiveCodeBench": 4.16,
157
+ "MT-Bench": 3.19,
158
+ "Alpaca": 3.06,
159
+ "Arena-Hard v2": 2.72,
160
+ },
161
+ "DFlash": {
162
+ "GSM8K": 5.45,
163
+ "MATH-500": 5.04,
164
+ "AIME25": 4.22,
165
+ "MBPP": 4.39,
166
+ "HumanEval": 4.95,
167
+ "LiveCodeBench": 3.70,
168
+ "MT-Bench": 2.98,
169
+ "Alpaca": 2.84,
170
+ "Arena-Hard v2": 2.59,
171
+ },
172
+ "DSpark": {
173
+ "GSM8K": 6.05,
174
+ "MATH-500": 5.78,
175
+ "AIME25": 5.12,
176
+ "MBPP": 5.11,
177
+ "HumanEval": 5.64,
178
+ "LiveCodeBench": 4.51,
179
+ "MT-Bench": 3.49,
180
+ "Alpaca": 3.35,
181
+ "Arena-Hard v2": 2.92,
182
+ },
183
+ },
184
+ }
185
+
186
+ MODELS = [
187
+ ("DSpark", "Qwen3-4B", "deepseek-ai/dspark_qwen3_4b_block7", 1.393, "Qwen3DSparkModel", "block7", "5", "yes", "Markov rank 256"),
188
+ ("DSpark", "Qwen3-8B", "deepseek-ai/dspark_qwen3_8b_block7", 2.371, "Qwen3DSparkModel", "block7", "5", "yes", "Markov rank 256"),
189
+ ("DSpark", "Qwen3-14B", "deepseek-ai/dspark_qwen3_14b_block7", 3.416, "Qwen3DSparkModel", "block7", "5", "yes", "Markov rank 256"),
190
+ ("DSpark", "Gemma4-12B", "deepseek-ai/dspark_gemma4_12b_block7", 3.430, "Gemma4DSparkModel", "block7", "5", "yes", "Markov rank 256"),
191
+ ("DFlash", "Qwen3-4B", "deepseek-ai/dflash_qwen3_4b_block7", 1.315, "Qwen3DSparkModel", "block7", "5", "no", "parallel block"),
192
+ ("DFlash", "Qwen3-8B", "deepseek-ai/dflash_qwen3_8b_block7", 2.293, "Qwen3DSparkModel", "block7", "5", "no", "parallel block"),
193
+ ("DFlash", "Qwen3-14B", "deepseek-ai/dflash_qwen3_14b_block7", 3.338, "Qwen3DSparkModel", "block7", "5", "no", "parallel block"),
194
+ ("DFlash", "Gemma4-12B", "deepseek-ai/dflash_gemma4_12b_block7", 3.296, "Gemma4DSparkModel", "block7", "5", "no", "parallel block"),
195
+ ("EAGLE-3", "Qwen3-4B", "deepseek-ai/eagle3_qwen3_4b_ttt7", 0.927, "Qwen3Eagle3Model", "ttt7", "1", "no", "training-time test"),
196
+ ("EAGLE-3", "Qwen3-8B", "deepseek-ai/eagle3_qwen3_8b_ttt7", 1.547, "Qwen3Eagle3Model", "ttt7", "1", "no", "training-time test"),
197
+ ("EAGLE-3", "Qwen3-14B", "deepseek-ai/eagle3_qwen3_14b_ttt7", 2.054, "Qwen3Eagle3Model", "ttt7", "1", "no", "training-time test"),
198
+ ("EAGLE-3", "Gemma4-12B", "deepseek-ai/eagle3_gemma4_12b_ttt7", 2.362, "Gemma4Eagle3Model", "ttt7", "1", "no", "training-time test"),
199
+ ]
200
+
201
+ FAMILY_COPY = {
202
+ "DSpark": {
203
+ "tag": "semi-autoregressive",
204
+ "summary": "Parallel DFlash-style backbone plus a lightweight Markov head and confidence scheduler.",
205
+ "strength": "Best accepted length in the released table and designed for load-aware serving.",
206
+ "tradeoff": "More machinery than a pure block drafter.",
207
+ },
208
+ "DFlash": {
209
+ "tag": "parallel block diffusion",
210
+ "summary": "Predicts a full block in one pass with target-feature conditioning and KV injection.",
211
+ "strength": "Very low drafting latency and strong first-token accuracy.",
212
+ "tradeoff": "Suffix tokens decay because positions are predicted independently.",
213
+ },
214
+ "EAGLE-3": {
215
+ "tag": "autoregressive feature drafter",
216
+ "summary": "Uses training-time test and fused target features to improve classic EAGLE drafting.",
217
+ "strength": "Strong lossless speculative baseline with stable sequential dependency modeling.",
218
+ "tradeoff": "Drafting cost scales with lookahead length.",
219
+ },
220
+ }
221
+
222
+ LEXICON = {
223
+ "GSM8K": "therefore the total is because each group contributes remaining answer equals final".split(),
224
+ "MATH-500": "let x satisfy equation substitute simplify bound hence root value proof".split(),
225
+ "AIME25": "triangle integer modulo sequence polynomial area count radius answer".split(),
226
+ "MBPP": "def return list index loop condition append result function test".split(),
227
+ "HumanEval": "class function assert edge case input output sorted recursive".split(),
228
+ "LiveCodeBench": "stdin parse graph dp binary search modulo constraints optimize".split(),
229
+ "MT-Bench": "I would compare the tradeoff and explain the practical implication".split(),
230
+ "Alpaca": "Here is a concise response with steps context and caveats".split(),
231
+ "Arena-Hard v2": "The best answer balances reasoning specificity and directness".split(),
232
+ }
233
+
234
+
235
+ def pct_gain(new, old):
236
+ if not old:
237
+ return 0.0
238
+ return (new / old - 1.0) * 100.0
239
+
240
+
241
+ def domain_for_task(task):
242
+ for domain, tasks in DOMAINS.items():
243
+ if task in tasks:
244
+ return domain
245
+ return "Mixed"
246
+
247
+
248
+ def model_rows():
249
+ rows = []
250
+ for family, target, repo, params, arch, horizon, layers, confidence, seq in MODELS:
251
+ rows.append([family, target, repo, f"{params:.3f}B", arch, horizon, layers, confidence, seq])
252
+ return rows
253
+
254
+
255
+ def benchmark_rows(target):
256
+ rows = []
257
+ for task in TASKS:
258
+ row = [task, domain_for_task(task)]
259
+ for method in METHODS:
260
+ row.append(f"{ACCEPTANCE[target][method][task]:.2f}")
261
+ rows.append(row)
262
+ return rows
263
+
264
+
265
+ def method_tau(target, method, task):
266
+ return ACCEPTANCE[target][method][task]
267
+
268
+
269
+ def simulated_tps(tau, method, baseline_tps, load):
270
+ load_pressure = max(0.0, min(1.0, (load - 1.0) / 99.0))
271
+ overhead = {"DSpark": 0.11, "DFlash": 0.10, "EAGLE-3": 0.19}[method]
272
+ waste = {"DSpark": 0.06, "DFlash": 0.24, "EAGLE-3": 0.16}[method]
273
+ return baseline_tps * tau / (1.0 + overhead) * (1.0 - load_pressure * waste)
274
+
275
+
276
+ def metric_cards(target, task, method, baseline_tps, load):
277
+ dspark = method_tau(target, "DSpark", task)
278
+ dflash = method_tau(target, "DFlash", task)
279
+ eagle = method_tau(target, "EAGLE-3", task)
280
+ best_base = max(dflash, eagle)
281
+ selected = method_tau(target, method, task)
282
+ calls_saved = (1.0 - 1.0 / selected) * 100.0
283
+ selected_tps = simulated_tps(selected, method, baseline_tps, load)
284
+ domain = domain_for_task(task)
285
+ return f"""
286
+ <div class="metric-grid">
287
+ <div class="metric-card accent-dspark">
288
+ <span>DSpark accepted length</span>
289
+ <strong>{dspark:.2f}</strong>
290
+ <small>{pct_gain(dspark, best_base):+.1f}% vs strongest baseline on {task}</small>
291
+ </div>
292
+ <div class="metric-card accent-orange">
293
+ <span>{method} simulated rate</span>
294
+ <strong>{selected_tps:.1f}</strong>
295
+ <small>tokens/sec from a {baseline_tps:.1f} baseline input</small>
296
+ </div>
297
+ <div class="metric-card accent-violet">
298
+ <span>Target calls avoided</span>
299
+ <strong>{calls_saved:.1f}%</strong>
300
+ <small>estimated from accepted length tau={selected:.2f}</small>
301
+ </div>
302
+ <div class="metric-card accent-blue">
303
+ <span>Benchmark profile</span>
304
+ <strong>{domain}</strong>
305
+ <small>EAGLE-3 {eagle:.2f} / DFlash {dflash:.2f} / DSpark {dspark:.2f}</small>
306
+ </div>
307
+ </div>
308
+ """
309
+
310
+
311
+ def acceptance_bar(target, task):
312
+ values = [method_tau(target, method, task) for method in METHODS]
313
+ fig = go.Figure()
314
+ fig.add_bar(
315
+ x=METHODS,
316
+ y=values,
317
+ marker_color=[COLORS[method] for method in METHODS],
318
+ text=[f"{v:.2f}" for v in values],
319
+ textposition="outside",
320
+ hovertemplate="%{x}<br>Accepted length: %{y:.2f}<extra></extra>",
321
+ )
322
+ fig.update_layout(
323
+ title=f"Accepted length per verification round on {target} / {task}",
324
+ yaxis_title="Accepted length, including target bonus token",
325
+ xaxis_title="Draft family",
326
+ height=360,
327
+ margin=dict(l=35, r=20, t=55, b=35),
328
+ paper_bgcolor="rgba(0,0,0,0)",
329
+ plot_bgcolor="rgba(0,0,0,0)",
330
+ font=dict(color="#dbeafe"),
331
+ yaxis=dict(gridcolor="rgba(148,163,184,0.18)", range=[0, max(values) + 1.0]),
332
+ )
333
+ return fig
334
+
335
+
336
+ def acceptance_heatmap(target):
337
+ z = [[method_tau(target, method, task) for task in TASKS] for method in METHODS]
338
+ fig = go.Figure(
339
+ data=go.Heatmap(
340
+ z=z,
341
+ x=TASKS,
342
+ y=METHODS,
343
+ colorscale=[
344
+ [0.0, "#111827"],
345
+ [0.35, "#1d4ed8"],
346
+ [0.65, "#14b8a6"],
347
+ [1.0, "#fbbf24"],
348
+ ],
349
+ text=[[f"{v:.2f}" for v in row] for row in z],
350
+ texttemplate="%{text}",
351
+ hovertemplate="%{y}<br>%{x}: %{z:.2f}<extra></extra>",
352
+ colorbar=dict(title="tau"),
353
+ )
354
+ )
355
+ fig.update_layout(
356
+ title=f"DeepSpec Table 1 matrix for {target}",
357
+ height=405,
358
+ margin=dict(l=75, r=25, t=55, b=70),
359
+ paper_bgcolor="rgba(0,0,0,0)",
360
+ plot_bgcolor="rgba(0,0,0,0)",
361
+ font=dict(color="#dbeafe"),
362
+ xaxis=dict(tickangle=-30),
363
+ )
364
+ return fig
365
+
366
+
367
+ def production_plot():
368
+ fig = go.Figure()
369
+ fig.add_trace(
370
+ go.Scatter(
371
+ x=[80, 120],
372
+ y=[51, 661],
373
+ mode="lines+markers+text",
374
+ name="V4-Flash",
375
+ text=["+51%", "+661%"],
376
+ textposition="top center",
377
+ line=dict(color="#14b8a6", width=3),
378
+ marker=dict(size=12),
379
+ hovertemplate="V4-Flash SLA %{x} tok/s/user<br>Throughput uplift %{y}%<extra></extra>",
380
+ )
381
+ )
382
+ fig.add_trace(
383
+ go.Scatter(
384
+ x=[35, 50],
385
+ y=[52, 406],
386
+ mode="lines+markers+text",
387
+ name="V4-Pro",
388
+ text=["+52%", "+406%"],
389
+ textposition="top center",
390
+ line=dict(color="#f97316", width=3),
391
+ marker=dict(size=12),
392
+ hovertemplate="V4-Pro SLA %{x} tok/s/user<br>Throughput uplift %{y}%<extra></extra>",
393
+ )
394
+ )
395
+ fig.update_layout(
396
+ title="Production DSpark frontier reported for DeepSeek-V4",
397
+ xaxis_title="Interactivity SLA anchor, tok/s/user",
398
+ yaxis_title="Aggregate throughput uplift vs MTP-1",
399
+ height=380,
400
+ margin=dict(l=45, r=25, t=55, b=45),
401
+ paper_bgcolor="rgba(0,0,0,0)",
402
+ plot_bgcolor="rgba(0,0,0,0)",
403
+ font=dict(color="#dbeafe"),
404
+ yaxis=dict(gridcolor="rgba(148,163,184,0.18)"),
405
+ legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0),
406
+ )
407
+ return fig
408
+
409
+
410
+ def inventory_plot():
411
+ fig = go.Figure()
412
+ for method in METHODS:
413
+ xs = [target for fam, target, *_ in MODELS if fam == method]
414
+ ys = [params for fam, _target, _repo, params, *_rest in MODELS if fam == method]
415
+ repos = [repo for fam, _target, repo, *_ in MODELS if fam == method]
416
+ fig.add_trace(
417
+ go.Scatter(
418
+ x=xs,
419
+ y=ys,
420
+ mode="markers+lines",
421
+ name=method,
422
+ marker=dict(size=14, color=COLORS[method]),
423
+ line=dict(color=COLORS[method], width=2),
424
+ text=repos,
425
+ hovertemplate="%{text}<br>Draft params %{y:.3f}B<extra></extra>",
426
+ )
427
+ )
428
+ fig.update_layout(
429
+ title="Released draft-module parameter scale",
430
+ yaxis_title="Draft module parameters, billions",
431
+ xaxis_title="Target model family",
432
+ height=380,
433
+ margin=dict(l=45, r=25, t=55, b=45),
434
+ paper_bgcolor="rgba(0,0,0,0)",
435
+ plot_bgcolor="rgba(0,0,0,0)",
436
+ font=dict(color="#dbeafe"),
437
+ yaxis=dict(gridcolor="rgba(148,163,184,0.18)"),
438
+ legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0),
439
+ )
440
+ return fig
441
+
442
+
443
+ def architecture_panel():
444
+ cards = []
445
+ for method in METHODS:
446
+ info = FAMILY_COPY[method]
447
+ cards.append(
448
+ f"""
449
+ <div class="arch-card" style="--accent:{COLORS[method]}">
450
+ <div class="arch-top">
451
+ <span>{html.escape(info["tag"])}</span>
452
+ <strong>{method}</strong>
453
+ </div>
454
+ <p>{html.escape(info["summary"])}</p>
455
+ <div class="arch-detail"><b>Strength</b>{html.escape(info["strength"])}</div>
456
+ <div class="arch-detail"><b>Tradeoff</b>{html.escape(info["tradeoff"])}</div>
457
+ </div>
458
+ """
459
+ )
460
+ return f"""
461
+ <div class="arch-grid">{''.join(cards)}</div>
462
+ <div class="pipeline">
463
+ <div><b>Target</b><span>prefill + bonus token</span></div>
464
+ <i></i>
465
+ <div><b>Draft</b><span>block proposal</span></div>
466
+ <i></i>
467
+ <div><b>Schedule</b><span>confidence prefix</span></div>
468
+ <i></i>
469
+ <div><b>Verify</b><span>lossless target check</span></div>
470
+ </div>
471
+ """
472
+
473
+
474
+ def source_panel():
475
+ return """
476
+ <div class="source-panel">
477
+ <b>Research basis</b>
478
+ <span>The 12 checkpoint pages have no individual model cards; the DeepSpec GitHub README identifies them as the released checkpoints used for Table 1 in the DSpark paper. The app uses that table for accepted-length metrics, the public HF API for checkpoint metadata, and the DSpark/DFlash/EAGLE-3 papers for architecture notes.</span>
479
+ <a href="https://github.com/deepseek-ai/DeepSpec" target="_blank">DeepSpec repo</a>
480
+ <a href="https://github.com/deepseek-ai/DeepSpec/blob/main/DSpark_paper.pdf" target="_blank">DSpark paper</a>
481
+ <a href="https://arxiv.org/abs/2602.06036" target="_blank">DFlash paper</a>
482
+ <a href="https://arxiv.org/abs/2503.01840" target="_blank">EAGLE-3 paper</a>
483
+ <a href="https://huggingface.co/collections/deepseek-ai/deepspec-6a410e3f1831ca8ca801b88b" target="_blank">DeepSpec collection</a>
484
+ </div>
485
+ """
486
+
487
+
488
+ def weighted_acceptance_count(rng, tau, method, scheduled_len, load):
489
+ draft_mean = max(0.0, tau - 1.0)
490
+ jitter = rng.uniform(-0.75, 0.75)
491
+ if method == "DFlash":
492
+ jitter -= max(0.0, (load - 65.0) / 140.0)
493
+ elif method == "EAGLE-3":
494
+ jitter -= max(0.0, (load - 80.0) / 220.0)
495
+ else:
496
+ jitter += max(0.0, (load - 80.0) / 260.0)
497
+ accepted = int(round(draft_mean + jitter))
498
+ return max(0, min(scheduled_len, accepted))
499
+
500
+
501
+ def scheduled_length(method, tau, load):
502
+ load_pressure = max(0.0, min(1.0, (load - 1.0) / 99.0))
503
+ if method == "DSpark":
504
+ confident = max(2, min(7, int(round(tau + 1.5))))
505
+ return max(2, int(round(confident - load_pressure * 2.0)))
506
+ if method == "DFlash":
507
+ return 7
508
+ return max(3, min(7, int(round(tau + 0.5))))
509
+
510
+
511
+ def simulate_tokens(target, task, method, output_tokens, load, seed, prompt):
512
+ rng = random.Random(f"{target}|{task}|{method}|{seed}|{prompt}")
513
+ vocab = list(LEXICON[task])
514
+ if prompt.strip():
515
+ prompt_words = [w.strip(".,:;!?()[]{}<>").lower() for w in prompt.split()]
516
+ vocab.extend([w for w in prompt_words if 2 < len(w) < 18])
517
+ tau = method_tau(target, method, task)
518
+ emitted = 0
519
+ cycle = 1
520
+ rows = []
521
+ while emitted < output_tokens and cycle <= 16:
522
+ sched = scheduled_length(method, tau, load)
523
+ accepted = weighted_acceptance_count(rng, tau, method, sched, load)
524
+ rejected = None if accepted >= sched else accepted
525
+ token_spans = []
526
+ for idx in range(7):
527
+ token = html.escape(rng.choice(vocab))
528
+ if idx < accepted:
529
+ cls = "tok accepted"
530
+ label = "accepted"
531
+ elif idx == rejected:
532
+ cls = "tok rejected"
533
+ label = "rejected"
534
+ elif idx >= sched:
535
+ cls = "tok dropped"
536
+ label = "not verified"
537
+ else:
538
+ cls = "tok tail"
539
+ label = "discarded suffix"
540
+ token_spans.append(f"<span class='{cls}' title='{label}'>{token}</span>")
541
+ bonus = html.escape(rng.choice(vocab))
542
+ token_spans.append(f"<span class='tok bonus' title='target bonus token'>{bonus}</span>")
543
+ emitted += accepted + 1
544
+ rows.append(
545
+ f"""
546
+ <div class="cycle-row">
547
+ <div class="cycle-id">round {cycle}</div>
548
+ <div class="token-strip">{''.join(token_spans)}</div>
549
+ <div class="cycle-stat">{accepted}+1 emitted</div>
550
+ </div>
551
+ """
552
+ )
553
+ cycle += 1
554
+ return f"""
555
+ <div class="sim-head">
556
+ <div><b>{method}</b><span>{target} / {task} / load {load:.0f}%</span></div>
557
+ <div class="legend"><span class="dot accepted"></span>accepted <span class="dot rejected"></span>first reject <span class="dot dropped"></span>pruned <span class="dot bonus"></span>target bonus</div>
558
+ </div>
559
+ <div class="simulator">{''.join(rows)}</div>
560
+ """
561
+
562
+
563
+ def production_cards():
564
+ return """
565
+ <div class="metric-grid compact">
566
+ <div class="metric-card accent-dspark"><span>V4-Flash moderate SLA</span><strong>+51%</strong><small>aggregate throughput at 80 tok/s/user</small></div>
567
+ <div class="metric-card accent-dspark"><span>V4-Flash matched capacity</span><strong>+60-85%</strong><small>faster per-user generation</small></div>
568
+ <div class="metric-card accent-orange"><span>V4-Pro moderate SLA</span><strong>+52%</strong><small>aggregate throughput at 35 tok/s/user</small></div>
569
+ <div class="metric-card accent-orange"><span>V4-Pro matched capacity</span><strong>+57-78%</strong><small>faster per-user generation</small></div>
570
+ </div>
571
+ """
572
+
573
+
574
+ def render_all(target, task, method, output_tokens, baseline_tps, load, seed, prompt):
575
+ return (
576
+ metric_cards(target, task, method, baseline_tps, load),
577
+ simulate_tokens(target, task, method, int(output_tokens), load, int(seed), prompt or ""),
578
+ acceptance_bar(target, task),
579
+ acceptance_heatmap(target),
580
+ benchmark_rows(target),
581
+ inventory_plot(),
582
+ architecture_panel(),
583
+ production_cards(),
584
+ production_plot(),
585
+ source_panel(),
586
+ )
587
+
588
+
589
+ CSS = """
590
+ :root {
591
+ --bg: #070b13;
592
+ --panel: rgba(15, 23, 42, 0.86);
593
+ --line: rgba(148, 163, 184, 0.18);
594
+ --text: #e5efff;
595
+ --muted: #9fb0c8;
596
+ }
597
+ .gradio-container {
598
+ background:
599
+ radial-gradient(circle at 12% 0%, rgba(20, 184, 166, 0.18), transparent 28%),
600
+ linear-gradient(135deg, #070b13 0%, #0f172a 52%, #111827 100%);
601
+ color: var(--text);
602
+ }
603
+ .main-shell {
604
+ border: 1px solid var(--line);
605
+ border-radius: 8px;
606
+ padding: 24px;
607
+ background: linear-gradient(145deg, rgba(15, 23, 42, 0.94), rgba(17, 24, 39, 0.78));
608
+ box-shadow: 0 24px 80px rgba(0, 0, 0, 0.28);
609
+ }
610
+ .hero-title {
611
+ display: grid;
612
+ grid-template-columns: 1.25fr 0.75fr;
613
+ gap: 18px;
614
+ align-items: stretch;
615
+ }
616
+ .hero-title h1 {
617
+ margin: 0;
618
+ font-size: clamp(2.1rem, 4vw, 4.5rem);
619
+ line-height: 0.92;
620
+ letter-spacing: 0;
621
+ }
622
+ .hero-title p {
623
+ color: var(--muted);
624
+ max-width: 760px;
625
+ font-size: 1rem;
626
+ }
627
+ .hero-stats {
628
+ display: grid;
629
+ grid-template-columns: repeat(2, minmax(0, 1fr));
630
+ gap: 10px;
631
+ }
632
+ .hero-stat {
633
+ border: 1px solid var(--line);
634
+ border-radius: 8px;
635
+ padding: 14px;
636
+ background: rgba(2, 6, 23, 0.36);
637
+ }
638
+ .hero-stat b {
639
+ display: block;
640
+ font-size: 1.55rem;
641
+ color: #ffffff;
642
+ }
643
+ .hero-stat span {
644
+ color: var(--muted);
645
+ font-size: 0.82rem;
646
+ }
647
+ .metric-grid {
648
+ display: grid;
649
+ grid-template-columns: repeat(4, minmax(0, 1fr));
650
+ gap: 12px;
651
+ }
652
+ .metric-grid.compact {
653
+ margin-bottom: 14px;
654
+ }
655
+ .metric-card {
656
+ border: 1px solid var(--line);
657
+ border-radius: 8px;
658
+ padding: 14px;
659
+ min-height: 118px;
660
+ background: rgba(2, 6, 23, 0.42);
661
+ position: relative;
662
+ overflow: hidden;
663
+ }
664
+ .metric-card:before {
665
+ content: "";
666
+ position: absolute;
667
+ inset: 0 auto 0 0;
668
+ width: 4px;
669
+ background: var(--accent, #38bdf8);
670
+ }
671
+ .metric-card span, .metric-card small {
672
+ display: block;
673
+ color: var(--muted);
674
+ }
675
+ .metric-card strong {
676
+ display: block;
677
+ margin: 6px 0;
678
+ font-size: 2rem;
679
+ color: #ffffff;
680
+ }
681
+ .accent-dspark { --accent: #14b8a6; }
682
+ .accent-orange { --accent: #f97316; }
683
+ .accent-violet { --accent: #8b5cf6; }
684
+ .accent-blue { --accent: #38bdf8; }
685
+ .sim-head {
686
+ display: flex;
687
+ align-items: center;
688
+ justify-content: space-between;
689
+ gap: 12px;
690
+ margin: 8px 0 12px;
691
+ }
692
+ .sim-head span {
693
+ display: block;
694
+ color: var(--muted);
695
+ }
696
+ .legend {
697
+ color: var(--muted);
698
+ font-size: 0.86rem;
699
+ }
700
+ .dot {
701
+ width: 10px;
702
+ height: 10px;
703
+ border-radius: 50%;
704
+ display: inline-block;
705
+ margin: 0 5px 0 12px;
706
+ }
707
+ .simulator {
708
+ display: grid;
709
+ gap: 8px;
710
+ }
711
+ .cycle-row {
712
+ display: grid;
713
+ grid-template-columns: 76px 1fr 96px;
714
+ gap: 10px;
715
+ align-items: center;
716
+ border: 1px solid var(--line);
717
+ border-radius: 8px;
718
+ padding: 10px;
719
+ background: rgba(15, 23, 42, 0.58);
720
+ }
721
+ .cycle-id, .cycle-stat {
722
+ color: var(--muted);
723
+ font-size: 0.82rem;
724
+ }
725
+ .token-strip {
726
+ display: flex;
727
+ flex-wrap: wrap;
728
+ gap: 6px;
729
+ }
730
+ .tok {
731
+ border: 1px solid transparent;
732
+ border-radius: 6px;
733
+ padding: 5px 8px;
734
+ font-size: 0.85rem;
735
+ line-height: 1.1;
736
+ }
737
+ .accepted, .tok.accepted { background: rgba(20, 184, 166, 0.18); color: #99f6e4; border-color: rgba(20, 184, 166, 0.36); }
738
+ .rejected, .tok.rejected { background: rgba(244, 63, 94, 0.18); color: #fecdd3; border-color: rgba(244, 63, 94, 0.38); }
739
+ .dropped, .tok.dropped { background: rgba(100, 116, 139, 0.18); color: #cbd5e1; border-color: rgba(148, 163, 184, 0.22); text-decoration: line-through; }
740
+ .tok.tail { background: rgba(249, 115, 22, 0.14); color: #fed7aa; border-color: rgba(249, 115, 22, 0.26); }
741
+ .bonus, .tok.bonus { background: rgba(56, 189, 248, 0.16); color: #bae6fd; border-color: rgba(56, 189, 248, 0.34); }
742
+ .arch-grid {
743
+ display: grid;
744
+ grid-template-columns: repeat(3, minmax(0, 1fr));
745
+ gap: 12px;
746
+ margin-bottom: 14px;
747
+ }
748
+ .arch-card {
749
+ border: 1px solid var(--line);
750
+ border-radius: 8px;
751
+ padding: 16px;
752
+ background: rgba(2, 6, 23, 0.42);
753
+ box-shadow: inset 0 3px 0 var(--accent);
754
+ }
755
+ .arch-top span {
756
+ color: var(--accent);
757
+ text-transform: uppercase;
758
+ font-size: 0.76rem;
759
+ }
760
+ .arch-top strong {
761
+ display: block;
762
+ color: #fff;
763
+ font-size: 1.3rem;
764
+ }
765
+ .arch-card p, .arch-detail {
766
+ color: var(--muted);
767
+ }
768
+ .arch-detail {
769
+ margin-top: 10px;
770
+ }
771
+ .arch-detail b {
772
+ display: block;
773
+ color: #e5efff;
774
+ }
775
+ .pipeline {
776
+ display: grid;
777
+ grid-template-columns: 1fr 24px 1fr 24px 1fr 24px 1fr;
778
+ gap: 8px;
779
+ align-items: center;
780
+ border: 1px solid var(--line);
781
+ border-radius: 8px;
782
+ padding: 14px;
783
+ background: rgba(15, 23, 42, 0.54);
784
+ }
785
+ .pipeline div {
786
+ min-height: 72px;
787
+ border-radius: 8px;
788
+ border: 1px solid rgba(148, 163, 184, 0.18);
789
+ padding: 12px;
790
+ background: rgba(2, 6, 23, 0.42);
791
+ }
792
+ .pipeline b, .pipeline span {
793
+ display: block;
794
+ }
795
+ .pipeline span {
796
+ color: var(--muted);
797
+ }
798
+ .pipeline i {
799
+ height: 2px;
800
+ background: linear-gradient(90deg, #14b8a6, #f97316);
801
+ }
802
+ .source-panel {
803
+ display: flex;
804
+ gap: 10px;
805
+ flex-wrap: wrap;
806
+ align-items: center;
807
+ border: 1px solid var(--line);
808
+ border-radius: 8px;
809
+ padding: 12px;
810
+ background: rgba(2, 6, 23, 0.34);
811
+ color: var(--muted);
812
+ }
813
+ .source-panel b {
814
+ color: #fff;
815
+ }
816
+ .source-panel span {
817
+ flex: 1 1 520px;
818
+ }
819
+ .source-panel a {
820
+ color: #67e8f9;
821
+ text-decoration: none;
822
+ border: 1px solid rgba(103, 232, 249, 0.22);
823
+ border-radius: 6px;
824
+ padding: 4px 8px;
825
+ }
826
+ @media (max-width: 900px) {
827
+ .hero-title, .metric-grid, .arch-grid, .pipeline {
828
+ grid-template-columns: 1fr;
829
+ }
830
+ .pipeline i {
831
+ height: 18px;
832
+ width: 2px;
833
+ margin-left: 12px;
834
+ }
835
+ .cycle-row {
836
+ grid-template-columns: 1fr;
837
+ }
838
+ }
839
+ """
840
+
841
+
842
+ with gr.Blocks(css=CSS, theme=gr.themes.Base()) as demo:
843
+ gr.HTML(
844
+ """
845
+ <div class="main-shell">
846
+ <div class="hero-title">
847
+ <div>
848
+ <h1>DeepSpec Decoding Lab</h1>
849
+ <p>Explore DeepSeek's 12 released draft modules across DSpark, DFlash, and EAGLE-3 with paper-backed accepted-length metrics, architecture comparisons, and a deterministic speculative-decoding simulator.</p>
850
+ </div>
851
+ <div class="hero-stats">
852
+ <div class="hero-stat"><b>12</b><span>released draft checkpoints</span></div>
853
+ <div class="hero-stat"><b>3</b><span>speculative-decoding families</span></div>
854
+ <div class="hero-stat"><b>9</b><span>benchmark tasks from Table 1</span></div>
855
+ <div class="hero-stat"><b>60-85%</b><span>reported V4-Flash per-user speed lift</span></div>
856
+ </div>
857
+ </div>
858
+ </div>
859
+ """
860
+ )
861
+
862
+ with gr.Row():
863
+ with gr.Column(scale=1, min_width=280):
864
+ target = gr.Dropdown(TARGETS, value="Qwen3-4B", label="Target family")
865
+ task = gr.Dropdown(TASKS, value="HumanEval", label="Benchmark profile")
866
+ method = gr.Radio(METHODS, value="DSpark", label="Primary draft family")
867
+ output_tokens = gr.Slider(24, 128, value=64, step=8, label="Simulation output budget")
868
+ baseline_tps = gr.Slider(5, 160, value=40, step=5, label="Autoregressive baseline tok/s")
869
+ load = gr.Slider(1, 100, value=70, step=1, label="Serving load pressure")
870
+ seed = gr.Number(value=7, label="Deterministic seed", precision=0)
871
+ prompt = gr.Textbox(
872
+ value="Write a compact function, then explain why it is correct.",
873
+ label="Prompt flavor",
874
+ lines=3,
875
+ )
876
+ run = gr.Button("Run Speculation", variant="primary")
877
+ with gr.Column(scale=3):
878
+ cards = gr.HTML()
879
+ sim = gr.HTML()
880
+
881
+ with gr.Tabs():
882
+ with gr.Tab("Benchmark Matrix"):
883
+ bar = gr.Plot()
884
+ heatmap = gr.Plot()
885
+ table = gr.Dataframe(
886
+ headers=["Task", "Domain", "DSpark", "DFlash", "EAGLE-3"],
887
+ datatype=["str", "str", "str", "str", "str"],
888
+ interactive=False,
889
+ wrap=True,
890
+ )
891
+ with gr.Tab("Checkpoint Inventory"):
892
+ inv_plot = gr.Plot()
893
+ inv_table = gr.Dataframe(
894
+ value=model_rows(),
895
+ headers=["Family", "Target", "Repo", "Params", "Architecture", "Horizon", "Layers", "Confidence", "Sequential signal"],
896
+ datatype=["str"] * 9,
897
+ interactive=False,
898
+ wrap=True,
899
+ )
900
+ with gr.Tab("Architectures"):
901
+ arch = gr.HTML()
902
+ with gr.Tab("Production Frontier"):
903
+ prod_cards = gr.HTML()
904
+ prod_plot = gr.Plot()
905
+ with gr.Tab("Sources"):
906
+ sources = gr.HTML()
907
+
908
+ outputs = [cards, sim, bar, heatmap, table, inv_plot, arch, prod_cards, prod_plot, sources]
909
+ inputs = [target, task, method, output_tokens, baseline_tps, load, seed, prompt]
910
+ demo.load(render_all, inputs=inputs, outputs=outputs, api_name=False)
911
+ for control in [target, task, method, output_tokens, baseline_tps, load, seed]:
912
+ control.change(render_all, inputs=inputs, outputs=outputs, api_name=False)
913
+ prompt.submit(render_all, inputs=inputs, outputs=outputs, api_name=False)
914
+ run.click(render_all, inputs=inputs, outputs=outputs, api_name="simulate")
915
+
916
+ demo.queue(default_concurrency_limit=8)
917
+
918
+ if __name__ == "__main__":
919
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio==6.10.0
2
+ plotly==5.24.1
rollout.jsonl ADDED
The diff for this file is too large to render. See raw diff