CoolFace
Apppublic

LisaMegaWatts/MonarchSLM

sourceHugging Facemitupdated 7mo agoView on Hugging Face
0likes
server.jl317 linesDownload Raw Back to root
1#=2server.jl — OpenAI-compatible inference server for MonarchSLM3 4Serves a Lux.jl trained Monarch Mixer model (sub-quadratic sequence mixing,5RMSNorm, SwiGLU, weight-tied). Downloads artifacts from HuggingFace on first run.6 7Endpoints:8    GET  /                       -> health check / API info9    GET  /v1/models              -> list available models10    POST /v1/chat/completions    -> generate text (OpenAI format, streaming supported)11=#12 13include("checkpoint.jl")14using HTTP15using UUIDs16using Downloads17 18# ═══════════════════════════════════════════════════════════════════19# Download artifacts from HuggingFace20# ═══════════════════════════════════════════════════════════════════21 22const CKPT_DIR = "checkpoints"23const CKPT_PATH = joinpath(CKPT_DIR, "final.jld2")24const CONFIG_PATH = joinpath(CKPT_DIR, "config.toml")25const VOCAB_PATH = joinpath(CKPT_DIR, "vocab.json")26const MERGES_PATH = joinpath(CKPT_DIR, "merges.txt")27const HF_REPO = get(ENV, "HF_REPO", "LisaMegaWatts/MonarchSLM")28const PORT = parse(Int, get(ENV, "PORT", "7860"))29 30function download_from_hf(repo::String, filename::String, local_path::String)31    url = "https://huggingface.co/$repo/resolve/main/$filename"32    println("Downloading $url ...")33    mkpath(dirname(local_path))34    Downloads.download(url, local_path)35    sz = round(filesize(local_path) / 1024^2, digits=1)36    println("  -> $local_path ($sz MB)")37end38 39function ensure_artifacts()40    for (localpath, remote) in [(CKPT_PATH, "final.jld2"),41                                (CONFIG_PATH, "config.toml"),42                                (VOCAB_PATH, "vocab.json")]43        if !isfile(localpath)44            println("No local $remote found, downloading from $HF_REPO ...")45            try46                download_from_hf(HF_REPO, remote, localpath)47            catch e48                println("Download failed for $remote: $e")49                println("Place $remote at $localpath manually.")50                exit(1)51            end52        end53    end54    if !isfile(MERGES_PATH)55        println("Attempting to download merges.txt (optional, for BPE) ...")56        try57            download_from_hf(HF_REPO, "merges.txt", MERGES_PATH)58        catch e59            println("  merges.txt not found (will use char tokenizer if vocab is array format)")60        end61    end62end63 64# ═══════════════════════════════════════════════════════════════════65# Download and load model66# ═══════════════════════════════════════════════════════════════════67 68ensure_artifacts()69 70println("\nLoading model...")71const INF_MODEL = load_inference_model(CKPT_PATH, CONFIG_PATH, VOCAB_PATH, MERGES_PATH)72const CONFIG = INF_MODEL.config73const PS = INF_MODEL.ps74const TOKENIZER = INF_MODEL.tokenizer75const CACHES = INF_MODEL.caches76const MODEL_CREATED_AT = Int(floor(time()))77 78println("\nModel ready: arch=$(CONFIG.arch), vocab=$(CONFIG.vocab_size), embd=$(CONFIG.embed_dim), " *79        "layers=$(CONFIG.n_layers), monarch_heads=$(CONFIG.n_monarch_heads), ctx=$(CONFIG.context_length)")80 81# ═══════════════════════════════════════════════════════════════════82# HTTP helpers83# ═══════════════════════════════════════════════════════════════════84 85const CORS_HEADERS = [86    "Access-Control-Allow-Origin" => "*",87    "Access-Control-Allow-Methods" => "GET, POST, OPTIONS",88    "Access-Control-Allow-Headers" => "Content-Type, Authorization",89]90 91function json_response(status::Int, body; extra_headers=[])92    json_bytes = JSON3.write(body)93    headers = [94        "Content-Type" => "application/json",95        CORS_HEADERS...,96        extra_headers...97    ]98    return HTTP.Response(status, headers, json_bytes)99end100 101function cors_preflight()102    return HTTP.Response(204, CORS_HEADERS)103end104 105# ═══════════════════════════════════════════════════════════════════106# Extract prompt from OpenAI chat messages107# ═══════════════════════════════════════════════════════════════════108 109function extract_prompt(messages)110    if isempty(messages)111        return ""112    end113    for i in length(messages):-1:1114        role = string(get(messages[i], :role, ""))115        if role == "user"116            return string(get(messages[i], :content, ""))117        end118    end119    return string(get(messages[end], :content, ""))120end121 122# ═══════════════════════════════════════════════════════════════════123# SSE helpers124# ═══════════════════════════════════════════════════════════════════125 126function sse_line(data)127    return "data: $(JSON3.write(data))\n\n"128end129 130# ═══════════════════════════════════════════════════════════════════131# Request handler132# ═══════════════════════════════════════════════════════════════════133 134function handle_request(request::HTTP.Request)135    method = request.method136    target = request.target137 138    if method == "OPTIONS"139        return cors_preflight()140    end141 142    # GET / — health check143    if method == "GET" && target == "/"144        return json_response(200, Dict(145            "name" => "MonarchSLM",146            "version" => "1.0.0",147            "description" => "A Monarch Mixer model trained on classical philosophy texts",148            "architecture" => "Decoder-only (Monarch Mixer, RMSNorm, SwiGLU, weight-tied)",149            "model" => Dict(150                "arch" => CONFIG.arch,151                "vocab_size" => CONFIG.vocab_size,152                "embed_dim" => CONFIG.embed_dim,153                "n_layers" => CONFIG.n_layers,154                "n_monarch_heads" => CONFIG.n_monarch_heads,155                "conv_kernel_size" => CONFIG.conv_kernel_size,156                "context_length" => CONFIG.context_length157            ),158            "endpoints" => ["/v1/models", "/v1/chat/completions"],159            "features" => ["streaming", "OpenAI-compatible", "top-k", "top-p"],160            "compatible_with" => ["OpenAI API", "OpenRouter"]161        ))162    end163 164    # GET /v1/models165    if method == "GET" && target == "/v1/models"166        return json_response(200, Dict(167            "object" => "list",168            "data" => [Dict(169                "id" => "monarchslm-philosophy",170                "object" => "model",171                "created" => MODEL_CREATED_AT,172                "owned_by" => "monarchslm"173            )]174        ))175    end176 177    # POST /v1/chat/completions178    if method == "POST" && target == "/v1/chat/completions"179        local body180        try181            body = JSON3.read(String(request.body))182        catch e183            return json_response(400, Dict("error" => Dict(184                "message" => "Invalid JSON in request body",185                "type" => "invalid_request_error",186                "code" => "invalid_json")))187        end188 189        temperature = Float64(clamp(get(body, :temperature, 0.8), 0.01, 2.0))190        max_tokens = Int(clamp(get(body, :max_tokens, 200), 1, CONFIG.context_length))191        top_k_val = Int(clamp(get(body, :top_k, 40), 0, CONFIG.vocab_size))192        top_p_val = Float64(clamp(get(body, :top_p, 1.0), 0.0, 1.0))193        stream = Bool(get(body, :stream, false))194 195        messages = get(body, :messages, [])196        prompt_text = extract_prompt(messages)197 198        if stream199            completion_id = "chatcmpl-" * string(uuid4())200            created = Int(floor(time()))201 202            buf = IOBuffer()203 204            initial_chunk = Dict(205                "id" => completion_id,206                "object" => "chat.completion.chunk",207                "created" => created,208                "model" => "monarchslm-philosophy",209                "choices" => [Dict(210                    "index" => 0,211                    "delta" => Dict("role" => "assistant", "content" => ""),212                    "finish_reason" => nothing213                )]214            )215            write(buf, sse_line(initial_chunk))216 217            token_count = Ref(0)218 219            generate_streaming(CONFIG, PS, TOKENIZER, prompt_text;220                               max_tokens, temperature, top_k=top_k_val, top_p=top_p_val,221                               caches=CACHES,222                               on_token = function(token_str)223                                   token_count[] += 1224                                   chunk = Dict(225                                       "id" => completion_id,226                                       "object" => "chat.completion.chunk",227                                       "created" => created,228                                       "model" => "monarchslm-philosophy",229                                       "choices" => [Dict(230                                           "index" => 0,231                                           "delta" => Dict("content" => token_str),232                                           "finish_reason" => nothing233                                       )]234                                   )235                                   write(buf, sse_line(chunk))236                               end)237 238            prompt_tokens = length(encode(TOKENIZER, prompt_text))239            finish_chunk = Dict(240                "id" => completion_id,241                "object" => "chat.completion.chunk",242                "created" => created,243                "model" => "monarchslm-philosophy",244                "choices" => [Dict(245                    "index" => 0,246                    "delta" => Dict(),247                    "finish_reason" => token_count[] >= max_tokens ? "length" : "stop"248                )],249                "usage" => Dict(250                    "prompt_tokens" => prompt_tokens,251                    "completion_tokens" => token_count[],252                    "total_tokens" => prompt_tokens + token_count[]253                )254            )255            write(buf, sse_line(finish_chunk))256            write(buf, "data: [DONE]\n\n")257 258            sse_body = take!(buf)259            headers = [260                "Content-Type" => "text/event-stream",261                "Cache-Control" => "no-cache",262                "X-Accel-Buffering" => "no",263                CORS_HEADERS...264            ]265            return HTTP.Response(200, headers, sse_body)266 267        else268            n_completions = Int(clamp(get(body, :n, 1), 1, 4))269 270            choices = []271            total_completion_tokens = 0272            for i in 1:n_completions273                text = generate_streaming(CONFIG, PS, TOKENIZER, prompt_text;274                                          max_tokens, temperature, top_k=top_k_val, top_p=top_p_val,275                                          caches=CACHES)276                finish_reason = "length"  # generate_streaming always produces exactly max_tokens tokens277                push!(choices, Dict(278                    "index" => i - 1,279                    "message" => Dict("role" => "assistant", "content" => text),280                    "finish_reason" => finish_reason))281                total_completion_tokens += max_tokens  # count tokens, not decoded chars282            end283 284            prompt_tokens = length(encode(TOKENIZER, prompt_text))285            return json_response(200, Dict(286                "id" => "chatcmpl-" * string(uuid4()),287                "object" => "chat.completion",288                "created" => Int(floor(time())),289                "model" => "monarchslm-philosophy",290                "choices" => choices,291                "usage" => Dict(292                    "prompt_tokens" => prompt_tokens,293                    "completion_tokens" => total_completion_tokens,294                    "total_tokens" => prompt_tokens + total_completion_tokens),295                "system_fingerprint" => "monarchslm-v1"))296        end297    end298 299    return json_response(404, Dict("error" => Dict(300        "message" => "Not found: $method $target",301        "type" => "invalid_request_error",302        "code" => "not_found")))303end304 305# ═══════════════════════════════════════════════════════════════════306# Start server307# ═══════════════════════════════════════════════════════════════════308 309println("\nMonarchSLM server starting on 0.0.0.0:$PORT ...")310println("  GET  http://localhost:$PORT/")311println("  GET  http://localhost:$PORT/v1/models")312println("  POST http://localhost:$PORT/v1/chat/completions")313println("  POST http://localhost:$PORT/v1/chat/completions  (stream=true)")314println()315 316HTTP.serve(handle_request, "0.0.0.0", PORT)317