NextStatNextStat

Сервер NextStat

Самостоятельный GPU API вывода

Автономный HTTP-сервер, предоставляющий движок статистического вывода NextStat через JSON REST API. Разверните на GPU-узле и делитесь со всей лабораторией — без настройки CUDA для каждого пользователя, без проблем с Python-окружениями.

Архитектура

┌─────────────┐    HTTP/JSON     ┌──────────────────────┐
│ Python      │ ◄──────────────► │  nextstat-server      │
│ thin client │    /v1/fit       │  (axum + tokio)       │
│ (httpx)     │    /v1/ranking   │                       │
└─────────────┘    /v1/batch/*   │  ┌─────────────────┐  │
                   /v1/models    │  │  Model Cache     │  │
  Jupyter / CI /   /v1/health    │  │  (LRU, SHA-256)  │  │
  Airflow / curl                 │  └─────────────────┘  │
                                 │  ┌─────────────────┐  │
                                 │  │  GPU Mutex Queue │  │
                                 │  │  CUDA / Metal    │  │
                                 │  └─────────────────┘  │
                                 └──────────────────────┘

Быстрый старт

# Сборка сервера (только CPU)
cargo build --release -p ns-server

# Запуск на порту 3742 (по умолчанию)
./target/release/nextstat-server

# С CUDA GPU
cargo build --release -p ns-server --features cuda
./target/release/nextstat-server --gpu cuda --port 8080

Python-клиент

Модуль nextstat.remote — тонкий клиент на чистом Python. Требуется только httpx — без Rust, без CUDA, без компилируемых расширений.

pip install httpx

import nextstat.remote as remote

client = remote.connect("http://gpu-server:3742")

# Один фит
result = client.fit(workspace_json)
print(f"μ̂ = {result.bestfit[0]:.4f} ± {result.uncertainties[0]:.4f}")

# Кеш модели: загрузить один раз, затем делать фиты без повторного парсинга
model_id = client.upload_model(workspace_json, name="my-analysis")
result = client.fit(model_id=model_id)  # примерно в 4 раза быстрее

# Батч-фит: несколько workspace в одном запросе
batch = client.batch_fit([ws1, ws2, ws3])
for r in batch.results:
    print(r.nll if r else "ошибка")

# Батч-тои: GPU-ускоренные псевдоэксперименты
toys = client.batch_toys(workspace_json, n_toys=10_000, seed=42)
print(f"{toys.n_converged}/{toys.n_toys} сошлось за {toys.wall_time_s:.1f}с")

# Ранкинг
ranking = client.ranking(workspace_json)
for e in ranking.entries:
    print(f"  {e.name}: Δμ = {e.delta_mu_up:+.4f} / {e.delta_mu_down:+.4f}")

Справка API

POST /v1/fit

Фит максимального правдоподобия. Автоопределение форматов pyhf и HS3 workspace. Передайте model_id вместо workspace для использования кешированной модели.

# Запрос
{
  "workspace": { ... },  // pyhf или HS3 (или опустите, если передан model_id)
  "model_id": "abc...",  // опционально, из POST /v1/models
  "gpu": true             // опционально, по умолчанию true
}

# Ответ
{
  "parameter_names": ["mu", "bkg_norm"],
  "poi_index": 0,
  "bestfit": [1.17, -0.03],
  "uncertainties": [1.00, 0.97],
  "nll": 6.908,
  "twice_nll": 13.816,
  "converged": true,
  "n_iter": 4,
  "n_fev": 6,
  "n_gev": 10,
  "covariance": [1.00, -0.66, -0.66, 0.95],
  "device": "cuda",
  "wall_time_s": 0.002
}

POST /v1/ranking

Ранкинг влияния мешающих параметров, отсортированный по |Δμ| по убыванию. Поддерживает model_id. Metal GPU пока не поддерживает ранкинг — сервер возвращает HTTP 400 с описательной ошибкой. Используйте CUDA или CPU для ранкинга.

# Запрос
{
  "workspace": { ... },  // или "model_id": "abc..."
  "gpu": true
}

# Ответ
{
  "entries": [
    {
      "name": "bkg_norm",
      "delta_mu_up": -0.71,
      "delta_mu_down": 0.68,
      "pull": -0.026,
      "constraint": 0.975
    }
  ],
  "device": "cuda",
  "wall_time_s": 0.001
}

POST /v1/batch/fit

Фит до 100 workspace'ов в одном запросе.

# Запрос
{ "workspaces": [{ ... }, { ... }], "gpu": true }

# Ответ
{
  "results": [
    { "index": 0, "bestfit": [...], "nll": 6.9, "converged": true, ... },
    { "index": 1, "error": "ошибка парсинга: ..." }
  ],
  "device": "cpu",
  "wall_time_s": 0.005
}

POST /v1/batch/toys

GPU-ускоренный батч-фиттинг тоев (CUDA, Metal или CPU (Rayon)).

# Запрос
{
  "workspace": { ... },
  "params": [1.0, 0.0],   // опционально, по умолчанию init модели
  "n_toys": 1000,          // по умолчанию 1000, максимум 100000
  "seed": 42,
  "gpu": true
}

# Ответ
{
  "n_toys": 1000,
  "n_converged": 998,
  "results": [{ "bestfit": [...], "nll": 7.1, "converged": true, "n_iter": 12 }, ...],
  "device": "cuda",
  "wall_time_s": 0.8
}

POST /v1/models

Загрузка workspace в кеш моделей. Возвращает SHA-256 model_id для использования в fit/ranking.

# Запрос
{ "workspace": { ... }, "name": "my-analysis" }

# Ответ
{ "model_id": "1fb0d639...", "n_params": 250, "n_channels": 5, "cached": true }

GET /v1/models

Список всех кешированных моделей с метаданными.

DELETE /v1/models/:id

Удаление модели из кеша.

GET /v1/health

{
  "status": "ok",
  "version": "0.9.0",
  "uptime_s": 3600.5,
  "device": "cuda",
  "inflight": 2,
  "total_requests": 1547,
  "cached_models": 3
}

Опции сервера

ФлагПо умолчаниюОписание
--port3742Порт прослушивания
--host0.0.0.0Адрес привязки
--gpunone"cuda" или "metal" (CPU если опущен)
--threads0 (auto)Количество CPU-потоков для не-GPU нагрузок

Сериализация GPU

Сервер принимает параллельные HTTP-соединения, но сериализует GPU-доступ черезtokio::sync::Mutex. Только один фит или ранкинг выполняется на GPU одновременно; остальные в очереди. HTTP-ответы неблокирующие — сервер может принимать новые запросы пока GPU-задача выполняется.

Выполнение инструментов (агентный интерфейс)

Сервер зеркалирует nextstat.tools через HTTP, позволяя агентам загружать определения инструментов и выполнять их без импорта Python.

GET /v1/tools/schema

Возвращает OpenAI-совместимые определения инструментов. Оболочка: schema_version = "nextstat.tool_schema.v1".

POST /v1/tools/execute

// Запрос
{
  "name": "nextstat_fit",
  "arguments": {
    "workspace_json": "{...}",
    "execution": { "deterministic": true }
  }
}

// Ответ (всегда оболочка результата инструмента)
{
  "schema_version": "nextstat.tool_result.v1",
  "ok": true,
  "result": { ... },
  "error": null,
  "meta": { "tool_name": "nextstat_fit", "nextstat_version": "..." }
}

Заметки о детерминизме

ns_compute::EvalMode является общим для процесса. Чтобы избежать гонок, сервер сериализует запросы вывода за глобальной блокировкой вычислений. Параметр на уровне запроса execution.eval_mode безопасен (нет утечек между запросами), но общая пропускная способность ниже (один запрос вывода за раз).

Политика GPU: если execution.deterministic=true (по умолчанию), инструменты выполняются на CPU. Если execution.deterministic=false и сервер запущен с --gpu cuda|metal, некоторые инструменты могут использовать GPU.

Python-клиент (транспорт: сервер)

from nextstat.tools import get_toolkit, execute_tool

server_url = "http://127.0.0.1:3742"
tools = get_toolkit(transport="server", server_url=server_url)

out = execute_tool(
    "nextstat_fit",
    {"workspace_json": "...", "execution": {"deterministic": True}},
    transport="server",
    server_url=server_url,
)
# server_url можно задать через переменную окружения NEXTSTAT_SERVER_URL
# fallback_to_local=False отключает локальный fallback

Безопасность / политика ввода

Серверный режим не предоставляет инструменты загрузки файлов (например, чтение ROOT-файлов из произвольных путей) через /v1/tools/*. Если вам нужна загрузка ROOT для демо-агента, делайте это на клиенте и отправляйте производные данные на сервер.

Развёртывание

Docker

# Сборка CPU
docker build -t nextstat-server -f crates/ns-server/Dockerfile .
docker run -p 3742:3742 nextstat-server

# Сборка CUDA
docker build --build-arg FEATURES=cuda -t nextstat-server:cuda   -f crates/ns-server/Dockerfile .
docker run -p 3742:3742 --gpus all nextstat-server:cuda --gpu cuda

Helm (Kubernetes)

helm install nextstat-server crates/ns-server/helm/nextstat-server \
  --set server.gpu=cuda \
  --set gpu.enabled=true \
  --set image.tag=0.9.0

Systemd

[Unit]
Description=Сервер NextStat для вывода на GPU
After=network.target

[Service]
ExecStart=/usr/local/bin/nextstat-server --gpu cuda --port 3742
Restart=always
Environment=RUST_LOG=info

[Install]
WantedBy=multi-user.target