File size: 8,104 Bytes
3bfff54
 
 
 
 
a3dd21d
3bfff54
 
 
 
a3dd21d
 
 
620167c
3bfff54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b31b620
 
3bfff54
 
a3dd21d
e41f1f2
bc9c08f
3bfff54
 
 
 
4553741
3bfff54
 
 
82fdf9e
a3dd21d
4553741
3bfff54
 
 
4553741
3bfff54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7f0f034
3bfff54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7f0f034
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3bfff54
 
7f0f034
3bfff54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a3dd21d
3bfff54
 
 
 
7f0f034
3bfff54
 
 
 
 
 
7f0f034
3bfff54
 
 
a3dd21d
3bfff54
 
 
 
a3dd21d
3bfff54
 
ea043fd
3bfff54
 
 
 
 
 
 
 
a3dd21d
 
 
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
"""
Dex Neural Bake — Tiny-Router Trainer (ZeroGPU / H200)
Trains the multi-head text classifier, exports to ONNX, and provides download.
"""

import json
import os
import sys
import shutil
import tempfile
from pathlib import Path

import gradio as gr
import spaces

# ── Add local package to path ──────────────────────────────────────────────
sys.path.insert(0, str(Path(__file__).parent))

from tiny_router.constants import HEAD_LABELS

# ── Training config ─────────────────────────────────────────────────────────
DEFAULT_ENCODER = "microsoft/MiniLM-L12-H384-uncased"
DEFAULT_EPOCHS = 10
DEFAULT_BATCH_SIZE = 32
DEFAULT_LR = 2e-5
DEFAULT_MAX_LENGTH = 128

DATA_DIR = Path(__file__).parent / "data" / "synthetic"
SCRIPTS_DIR = Path(__file__).parent / "scripts"


def _build_train_cmd(output_dir: str, encoder: str, epochs: int, batch_size: int, lr: float, max_length: int) -> list[str]:
    return [
        sys.executable, "-m", "scripts.train",
        "--train-file", str(DATA_DIR / "train.jsonl"),
        "--validation-file", str(DATA_DIR / "validation.jsonl"),
        "--output-dir", output_dir,
        "--encoder-name", encoder,
        "--batch-size", str(batch_size),
        "--epochs", str(epochs),
        "--encoder-lr", str(lr),
        "--head-lr", str(lr),
        "--max-length", str(max_length),
        "--device", "cuda",
    ]


def _build_eval_cmd(model_dir: str, output_file: str) -> list[str]:
    return [
        sys.executable, "-m", "scripts.eval",
        "--model-dir", model_dir,
        "--data-file", str(DATA_DIR / "test.jsonl"),
        "--output-file", output_file,
        "--device", "cuda",
    ]


def _build_export_cmd(model_dir: str, output_dir: str) -> list[str]:
    return [
        sys.executable, "-m", "scripts.export_onnx",
        "--model-dir", model_dir,
        "--output-dir", output_dir,
    ]


@spaces.GPU(duration=900)  # 15 min max for H200
def train_and_export(encoder: str, epochs: int, batch_size: int, lr: float, max_length: int):
    """Train on GPU, evaluate, export to ONNX, return results + model file."""
    import subprocess

    tmpdir = tempfile.mkdtemp(prefix="tiny-router-")
    model_dir = os.path.join(tmpdir, "checkpoint")
    onnx_path = os.path.join(tmpdir, "tiny-router.onnx")
    eval_out = os.path.join(tmpdir, "eval_results.json")

    logs = []

    # ── Train ───────────────────────────────────────────────────────────
    logs.append("🚀 Starting training on H200...")
    train_cmd = _build_train_cmd(model_dir, encoder, epochs, batch_size, lr, max_length)
    logs.append(f"Command: {' '.join(train_cmd)}")

    proc = subprocess.run(train_cmd, capture_output=True, text=True, cwd=str(Path(__file__).parent))
    logs.append(proc.stdout[-3000:] if proc.stdout else "")
    if proc.returncode != 0:
        logs.append(f"❌ Training failed:\n{proc.stderr[-2000:]}")
        return "\n".join(logs), None

    logs.append("✅ Training complete!")

    # ── Evaluate ────────────────────────────────────────────────────────
    logs.append("\n📊 Evaluating on test set...")
    eval_cmd = _build_eval_cmd(model_dir, eval_out)
    proc = subprocess.run(eval_cmd, capture_output=True, text=True, cwd=str(Path(__file__).parent))
    logs.append(proc.stdout[-2000:] if proc.stdout else "")
    if proc.returncode != 0:
        logs.append(f"⚠️ Eval warning:\n{proc.stderr[-1000:]}")

    eval_results = {}
    if os.path.exists(eval_out):
        with open(eval_out) as f:
            eval_results = json.load(f)
        logs.append(f"\n📈 Results: {json.dumps(eval_results, indent=2)}")

    # ── Push checkpoint to HF Hub ──────────────────────────────────────
    logs.append("\n📤 Pushing checkpoint to HF Hub...")
    try:
        from huggingface_hub import HfApi
        api = HfApi()
        repo_id = "Dexifried/tiny-router-checkpoint"
        api.create_repo(repo_id=repo_id, repo_type="model", exist_ok=True)
        api.upload_folder(
            folder_path=model_dir,
            repo_id=repo_id,
            repo_type="model",
            commit_message=f"Tiny-router checkpoint (encoder={encoder}, epochs={epochs})",
        )
        logs.append(f"✅ Pushed to https://huggingface.co/{repo_id}")
    except Exception as e:
        logs.append(f"⚠️ Push failed: {e}")

    summary = json.dumps(eval_results, indent=2) if eval_results else "No eval results"
    return "\n".join(logs), summary


def quick_predict(text: str):
    """Run inference with the trained model (CPU, fast for single texts)."""
    if not text.strip():
        return "Enter some text to classify."
    # This would use the ONNX model — for demo purposes, show the expected output format
    return json.dumps({
        "input": text,
        "note": "Use the downloaded ONNX model for local inference",
        "expected_output": {
            "relation_to_previous": {"label": "...", "confidence": 0.0},
            "actionability": {"label": "...", "confidence": 0.0},
            "retention": {"label": "...", "confidence": 0.0},
            "urgency": {"label": "...", "confidence": 0.0},
            "overall_confidence": 0.0,
        }
    }, indent=2)


# ── Gradio UI ───────────────────────────────────────────────────────────────
with gr.Blocks(title="⚡ Dex Neural Bake — Tiny-Router Trainer", theme=gr.themes.Soft()) as demo:
    gr.Markdown(
        "# ⚡ Tiny-Router Trainer (ZeroGPU / H200)\n\n"
        "Train the multi-head routing classifier on H200 GPU, export to ONNX, and download.\n\n"
        "**Heads:** `relation_to_previous` · `actionability` · `retention` · `urgency`"
    )

    with gr.Tab("🏋️ Train & Export"):
        with gr.Row():
            encoder = gr.Textbox(label="Encoder model", value=DEFAULT_ENCODER)
            epochs = gr.Slider(1, 30, value=DEFAULT_EPOCHS, step=1, label="Epochs")
        with gr.Row():
            batch_size = gr.Slider(8, 128, value=DEFAULT_BATCH_SIZE, step=8, label="Batch size")
            lr = gr.Number(label="Learning rate", value=DEFAULT_LR)
            max_length = gr.Slider(64, 512, value=DEFAULT_MAX_LENGTH, step=32, label="Max token length")

        train_btn = gr.Button("🚀 Train & Push to HF", variant="primary")
        train_log = gr.Textbox(label="Training log", lines=25, max_lines=50, interactive=False)
        eval_summary = gr.JSON(label="Evaluation results")

        train_btn.click(
            fn=train_and_export,
            inputs=[encoder, epochs, batch_size, lr, max_length],
            outputs=[train_log, eval_summary],
        )

    with gr.Tab("📋 Dataset Info"):
        gr.Markdown(
            f"**Training samples:** 2,279\n\n"
            f"**Validation samples:** 276\n\n"
            f"**Test samples:** 337\n\n"
            "### Label heads\n"
        )
        for head, labels in HEAD_LABELS.items():
            gr.Markdown(f"- **{head}**: {', '.join(labels)}")

    with gr.Tab("ℹ️ About"):
        gr.Markdown(
            "## Tiny-Router\n\n"
            "A compact multi-head text classifier for routing short messages in agent/product workflows.\n\n"
            "Based on [tiny-router](https://github.com/UdaraJay/tiny-router) by UdaraJay.\n\n"
            "**Architecture:** DeBERTa-v3-small / MiniLM encoder → mean pooling → 4 classification heads\n\n"
            "**Training:** H200 GPU via HuggingFace ZeroGPU (25 min/day Pro quota)"
        )

if __name__ == "__main__":
    demo.launch()