File size: 8,851 Bytes
4a6405d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
OpenMind CLI - Command Line Interface.

Commands:
  openmind download <model_id>           Download from HuggingFace hub
  openmind chat <model_dir>              Interactive terminal chat
  openmind serve <model_dir> --port 8000 Launch API server
  openmind eval <model_dir> --tasks ...  Run evaluation benchmarks
  openmind train --config <path>         Start training
  openmind convert <checkpoint_dir>      Convert checkpoint to HF format
"""

import os
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))

try:
    import typer
    from rich.console import Console
    from rich.panel import Panel
    from rich.progress import Progress, SpinnerColumn, TextColumn
    from rich.markdown import Markdown
    from rich.table import Table
except ImportError:
    print("CLI dependencies not installed. Run: pip install typer rich")
    sys.exit(1)

app = typer.Typer(
    name="openmind",
    help="🧠 OpenMind - Build, train, and serve your own LLM",
    add_completion=False,
)
console = Console()


@app.command()
def download(
    model_id: str = typer.Argument(..., help="HuggingFace model ID to download"),
    output_dir: str = typer.Option("models/", help="Output directory"),
):
    """Download a pretrained model from HuggingFace Hub."""
    console.print(Panel(f"Downloading [bold cyan]{model_id}[/]", title="OpenMind Download"))

    try:
        from huggingface_hub import snapshot_download
        local_path = snapshot_download(
            repo_id=model_id,
            local_dir=os.path.join(output_dir, model_id.split("/")[-1]),
            local_dir_use_symlinks=False,
        )
        console.print(f"\nβœ… Model downloaded to: [bold green]{local_path}[/]")
    except Exception as e:
        console.print(f"\n❌ Download failed: [bold red]{e}[/]")
        raise typer.Exit(1)


@app.command()
def chat(
    model_dir: str = typer.Argument(..., help="Path to model directory"),
    temperature: float = typer.Option(0.7, help="Sampling temperature"),
    max_tokens: int = typer.Option(256, help="Maximum tokens to generate"),
    top_k: int = typer.Option(50, help="Top-k sampling"),
    top_p: float = typer.Option(0.9, help="Nucleus sampling"),
):
    """Interactive terminal chat with the model."""
    import torch
    from src.models.modeling_openmind import OpenMindModel
    from src.data.tokenizer import BPETokenizer
    from src.data.chat_templates import format_chat

    console.print(Panel("🧠 OpenMind Interactive Chat", subtitle="Type 'quit' to exit"))

    with Progress(SpinnerColumn(), TextColumn("[bold blue]Loading model...")) as progress:
        task = progress.add_task("Loading", total=None)
        device = "cuda" if torch.cuda.is_available() else "cpu"
        model = OpenMindModel.from_pretrained(model_dir, device=device)
        model.eval()

        tokenizer_path = os.path.join(model_dir, "tokenizer")
        if os.path.exists(tokenizer_path):
            tokenizer = BPETokenizer.load(tokenizer_path)
        else:
            tokenizer = BPETokenizer(vocab_size=32000)

    console.print(f"[dim]Model loaded on {device}. Ready to chat![/dim]\n")

    messages = []

    while True:
        try:
            user_input = console.input("[bold cyan]You:[/] ")
        except (KeyboardInterrupt, EOFError):
            break

        if user_input.lower() in ("quit", "exit", "q"):
            break

        if not user_input.strip():
            continue

        messages.append({"role": "user", "content": user_input})
        prompt = format_chat(messages, add_generation_prompt=True)

        # Tokenize and generate
        input_ids = tokenizer.encode(prompt, allowed_special={"all"})
        input_tensor = torch.tensor([input_ids], dtype=torch.long).to(device)

        with torch.no_grad():
            output_ids = model.generate(
                input_tensor,
                max_new_tokens=max_tokens,
                temperature=temperature,
                top_k=top_k,
                top_p=top_p,
                eos_token_id=tokenizer.eos_token_id,
            )

        generated = output_ids[0, len(input_ids):].tolist()
        response = tokenizer.decode(generated)

        # Clean up special tokens from response
        for special in ["<|endoftext|>", "<|system|>", "<|user|>", "<|assistant|>"]:
            response = response.replace(special, "")
        response = response.strip()

        messages.append({"role": "assistant", "content": response})

        console.print(f"\n[bold green]OpenMind:[/] ", end="")
        try:
            console.print(Markdown(response))
        except Exception:
            console.print(response)
        console.print()

    console.print("\n[dim]Goodbye! πŸ‘‹[/dim]")


@app.command()
def serve(
    model_dir: str = typer.Argument(..., help="Path to model directory"),
    host: str = typer.Option("0.0.0.0", help="Host to bind"),
    port: int = typer.Option(8000, help="Port to bind"),
    device: str = typer.Option(None, help="Device (cuda/cpu/auto)"),
):
    """Launch the OpenAI-compatible API server."""
    console.print(Panel(
        f"Starting API server\n"
        f"Model: [bold]{model_dir}[/]\n"
        f"Endpoint: [bold cyan]http://{host}:{port}[/]",
        title="🌐 OpenMind Server",
    ))

    from src.inference.api_server import start_server
    start_server(model_dir, host, port, device)


@app.command(name="eval")
def evaluate(
    model_dir: str = typer.Argument(..., help="Path to model directory"),
    tasks: list[str] = typer.Option(None, help="Benchmark tasks to run"),
    fewshot: int = typer.Option(0, help="Number of few-shot examples"),
    max_examples: int = typer.Option(500, help="Max examples per benchmark"),
    output: str = typer.Option("results", help="Output directory"),
):
    """Run evaluation benchmarks on the model."""
    console.print(Panel("Running evaluation suite", title="πŸ”¬ OpenMind Eval"))

    from src.evaluation.run_eval import run_benchmark_suite

    if tasks is None:
        tasks = ["hellaswag", "arc_easy", "arc_challenge", "truthfulqa"]

    results = run_benchmark_suite(
        model_dir, tasks, fewshot, max_examples, output
    )

    # Display results table
    table = Table(title="Evaluation Results")
    table.add_column("Benchmark", style="cyan")
    table.add_column("Accuracy", style="green", justify="right")

    for task, res in results.get("tasks", {}).items():
        table.add_row(task, f"{res['accuracy']:.2%}")

    console.print(table)


@app.command()
def train(
    config: str = typer.Option("configs/base_config.yaml", help="Path to training config"),
):
    """Start model training."""
    console.print(Panel(f"Starting training with config: [bold]{config}[/]", title="πŸš€ OpenMind Train"))

    from src.training.train import main as train_main
    train_main(config)


@app.command()
def convert(
    checkpoint_dir: str = typer.Argument(..., help="Path to training checkpoint"),
    output_dir: str = typer.Option(None, help="Output directory (default: same dir)"),
):
    """Convert a training checkpoint to HuggingFace format."""
    import torch
    from src.models.modeling_openmind import OpenMindModel
    from src.models.config_openmind import OpenMindConfig

    console.print(Panel(f"Converting checkpoint: [bold]{checkpoint_dir}[/]", title="πŸ”„ Convert"))

    if output_dir is None:
        output_dir = checkpoint_dir + "-hf"

    config = OpenMindConfig.from_pretrained(checkpoint_dir)
    model = OpenMindModel(config)

    model_path = os.path.join(checkpoint_dir, "model.pt")
    if os.path.exists(model_path):
        state_dict = torch.load(model_path, map_location="cpu")
        model.load_state_dict(state_dict)
    else:
        console.print(f"[red]No model.pt found in {checkpoint_dir}[/]")
        raise typer.Exit(1)

    model.save_pretrained(output_dir)
    console.print(f"\nβœ… Converted model saved to: [bold green]{output_dir}[/]")


@app.command()
def info():
    """Display system and project information."""
    import torch

    table = Table(title="🧠 OpenMind System Info")
    table.add_column("Property", style="cyan")
    table.add_column("Value", style="green")

    table.add_row("Python", sys.version.split()[0])
    table.add_row("PyTorch", torch.__version__)
    table.add_row("CUDA Available", str(torch.cuda.is_available()))
    if torch.cuda.is_available():
        table.add_row("CUDA Version", torch.version.cuda or "N/A")
        table.add_row("GPU", torch.cuda.get_device_name(0))
        mem = torch.cuda.get_device_properties(0).total_mem / (1024**3)
        table.add_row("GPU Memory", f"{mem:.1f} GB")
    table.add_row("BF16 Supported", str(
        torch.cuda.is_available() and torch.cuda.is_bf16_supported()
    ))

    console.print(table)


if __name__ == "__main__":
    app()