Skip to content
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
0a42e6f
[Rollout] Patch InprocClient.get_output() to ensure correct behavior …
sfc-gh-yewang Feb 24, 2026
84636e6
[Rollout] Forest Cascade Attention (#20)
sfc-gh-yewang Feb 24, 2026
b1d2e0f
Bring ArcticForge code over (#21)
sfc-gh-mwyatt Feb 24, 2026
9729a45
Surface logprobs (#23)
sfc-gh-yewang Mar 3, 2026
b7aaf10
ArcticInference server weight sync (#22)
sfc-gh-yewang Mar 3, 2026
420418f
Multi-model support (#25)
sfc-gh-mwyatt Mar 5, 2026
b3a62c1
Extract multi-model support to separate module (#26)
sfc-gh-mwyatt Mar 6, 2026
6ab2f5a
Surface wake_up/sleep API (#27)
sfc-gh-yewang Mar 6, 2026
570d9e3
Update and expand server examples (#28)
sfc-gh-mwyatt Mar 6, 2026
8812d31
Fix FP8 MoE weight sync (#29)
sfc-gh-yewang Mar 6, 2026
d8473bd
Small fixes (#30)
sfc-gh-yewang Mar 6, 2026
d5ddae9
Update CODEOWNERS with new org and user names (#32)
jenkins-snowci-mergequeue-0-emu[bot] Mar 9, 2026
ca36437
SNOW-2231554-EMU Migration - Update references for Wave 4 testing (#18)
svc-gh-sourcegraph-sa Mar 9, 2026
62ca5b9
Revert "SNOW-2231554-EMU Migration - Update references for Wave 4 tes…
sfc-gh-yewang Mar 9, 2026
fad9d41
reflect vllm PR #32947 (#34)
sfc-gh-mhidayetoglu Mar 9, 2026
4dc4bbe
Add reverse mode for weight sync and fix per-replica port assignment …
sfc-gh-yewang Mar 13, 2026
7970df0
Fix spec decoding drafter model during sleep/wake cycles (#36)
sfc-gh-yewang Mar 16, 2026
6a6acb9
Add spec (drafter) model weight sync API (#37)
sfc-gh-yewang Mar 17, 2026
a591018
Fix shift-parallel CUDA graph capture and dispatch (#258)
sfc-gh-yewang Apr 6, 2026
830f92f
Feature: colocation support (#45)
sfc-gh-mwyatt Apr 8, 2026
f23d97c
feat: IPC shared-memory weight sync + placement group support for col…
sfc-gh-kganesan Apr 22, 2026
60bea68
Disable health status
sfc-gh-truwase May 12, 2026
3db6631
Verl colocate
sfc-gh-truwase May 15, 2026
e4d1740
Add reset_prefix_cache to inference worker (#73)
sfc-gh-mhidayetoglu May 19, 2026
7a4f7e9
PG fixes
sfc-gh-truwase May 19, 2026
99838c4
Merge branch 'tunji/verl_integration' into tunji/verl_32b_perf
sfc-gh-truwase May 19, 2026
883dc95
support VLLM_BATCH_INVARIANT
sfc-gh-sbekman May 20, 2026
85c762a
vllm version
sfc-gh-truwase May 20, 2026
9f9ac4a
support VLLM_BATCH_INVARIANT note
sfc-gh-sbekman May 20, 2026
c86fee9
Merge branch 'tunji/verl_integration' of https://github.com/snowflake…
sfc-gh-sbekman May 20, 2026
dfb4f0a
weight sync; fca
sfc-gh-truwase May 23, 2026
9aa69ae
Sync tunji/verl_integration with origin/main (vLLM 0.18.0 rebase) (#7…
sfc-gh-mhidayetoglu May 24, 2026
4f051c5
fix vllm18 rebase mistake
sfc-gh-mhidayetoglu May 24, 2026
07bb3e4
Fix vllm deps
sfc-gh-truwase May 24, 2026
4cfc9d2
fix parallel config init
sfc-gh-mhidayetoglu May 25, 2026
85391b1
Add strict weight-sync parameter name validation for CUDA IPC.
sfc-gh-truwase May 28, 2026
e80638d
Harden weight sync and add spec-weight HTTP endpoints
sfc-gh-truwase May 30, 2026
d4d0279
Add streaming CUDA IPC chunk loader for low-memory weight sync
sfc-gh-truwase Jun 2, 2026
63ee7e8
Print one-shot [FCA] status banner (#85)
sfc-gh-mhidayetoglu Jun 5, 2026
e70bd99
Add min_batch_size tuning to Forest Cascade Attention (#93)
sfc-gh-mhidayetoglu Jun 7, 2026
28a0f45
Strict group-balanced routing for GRPO rollouts (#92)
sfc-gh-mhidayetoglu Jun 9, 2026
c6f0a4f
add auxiliary compute_weight_norm API used in tests
sfc-gh-sbekman Jun 16, 2026
1edefdf
Integrate Zorro Inference (FCA) and Arctic Speculative Decoding into …
sfc-gh-mhidayetoglu Jun 17, 2026
6c73218
Add hard cut-off for speculative decoding at high batch size
sfc-gh-mhidayetoglu Jun 23, 2026
e51233f
Merge branch 'main' into arctic_rl/integration
sfc-gh-mhidayetoglu Jun 24, 2026
c771c22
fix codeowners
sfc-gh-mhidayetoglu Jun 24, 2026
9803ba3
delete dev patches
sfc-gh-mhidayetoglu Jun 24, 2026
d8365b6
remove kvcached
sfc-gh-mhidayetoglu Jun 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @sfc-gh-aqiao @sfc-gh-jrasley @sfc-gh-mhidayetoglu @sfc-gh-yewang @sfc-gh-goliaro @sfc-gh-reyazda
* @sfc-gh-aqiao @jeff-rasley_snow @mert-hidayetoglu_snow @ye-wang_snow @gabriele-oliaro_snow @reza-yazdani_snow
Comment thread
sfc-gh-mhidayetoglu marked this conversation as resolved.
Outdated
26 changes: 26 additions & 0 deletions arctic_inference/envs.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,24 @@

if TYPE_CHECKING:
ARCTIC_INFERENCE_SKIP_SPEC_MODEL_CHECK: bool = False
ARCTIC_FP32_LM_HEAD: bool = False


def arctic_inference_effective_enabled(
extra_env: dict[str, str] | None = None,
) -> bool:
"""True if the Arctic vLLM plugin should load for this process or worker env.

When ``extra_env`` is passed (e.g. ``ModelConfig.extra_env``), it is checked
in addition to ``os.environ`` so the driver can omit Arctic-only engine
kwargs when workers will not enable the plugin.
"""
if os.getenv("ARCTIC_INFERENCE_ENABLED", "0") == "1":
return True
if extra_env and str(extra_env.get("ARCTIC_INFERENCE_ENABLED", "0")) == "1":
return True
return False


environment_variables: dict[str, Callable[[], Any]] = {
"ARCTIC_INFERENCE_ENABLED":
Expand All @@ -28,6 +46,14 @@
lambda: os.getenv("ARCTIC_INFERENCE_SKIP_SPEC_MODEL_CHECK", "0") == "1",
"ARCTIC_INFERENCE_SKIP_VERSION_CHECK":
lambda: os.getenv("ARCTIC_INFERENCE_SKIP_VERSION_CHECK", "0") == "1",
# Run the lm_head matmul in fp32 (weights stay in their native
# dtype; we upcast on the fly). vLLM's V1 sampler already does
# softmax in fp32, so the full final stage is fp32. Needed for
# RL workloads that require precise log-probs / token
# probabilities for off-policy correction. Equivalent to the
# ``--fp32-lm-head`` CLI flag.
"ARCTIC_FP32_LM_HEAD":
lambda: os.getenv("ARCTIC_FP32_LM_HEAD", "0") == "1",
}

# temporary workaround for gpt-oss model
Expand Down
16 changes: 16 additions & 0 deletions arctic_inference/server/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from arctic_inference.server.config import ModelConfig
from arctic_inference.server.pipeline import Pipeline
from arctic_inference.server.replica_pool import ReplicaPool, ensure_ray
from arctic_inference.server.weight_sync import (
NCCLEngine, WeightInfo, build_weights_info,
)

__all__ = [
"ModelConfig",
"Pipeline",
"ReplicaPool",
"ensure_ray",
"NCCLEngine",
"WeightInfo",
"build_weights_info",
]
213 changes: 213 additions & 0 deletions arctic_inference/server/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
from __future__ import annotations

from contextlib import asynccontextmanager
from typing import Any

from arctic_inference.server.replica_pool import ReplicaPool
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field, model_validator

from arctic_inference.server.config import ModelConfig


# ---------------------------------------------------------------------------
# Request models
# ---------------------------------------------------------------------------


class InitRequest(BaseModel):
config: ModelConfig
model_id: str | None = None
num_replicas: int | None = None


class GenerateRequest(BaseModel):
model_id: str | None = None
prompts: list[str | list[int]]
sampling_params: dict[str, Any] = Field(default_factory=dict)
routing_key: str | list[str | None] | None = None
strict: bool = False


class GroupConfig(BaseModel):
group_id: int
master_addr: str
master_port: int
world_size: int
replica_ids: list[int]


class SleepRequest(BaseModel):
model_id: str
level: int = 1


class WakeUpRequest(BaseModel):
model_id: str
tags: list[str] | None = None


class SyncWeightsRequest(BaseModel):
model_id: str | None = None
groups: list[GroupConfig] | None = None
bucket_size: int = 256 * 1024 * 1024
strategy: str = "hotswap"
engine_only: bool = False
direct_mode: bool = False
reverse: bool = False

# Legacy flat fields
master_addr: str | None = None
master_port: int | None = None
world_size: int | None = None


class SyncSpecWeightsRequest(BaseModel):
model_id: str | None = None
groups: list[GroupConfig] | None = None
bucket_size: int = 256 * 1024 * 1024
strategy: str = "hotswap"
engine_only: bool = False
reverse: bool = False

# Legacy flat fields
master_addr: str | None = None
master_port: int | None = None
world_size: int | None = None


# ---------------------------------------------------------------------------
# Backend – a bare ReplicaPool by default, swapped to Driver by multi_model.py
# ---------------------------------------------------------------------------

backend: Any = ReplicaPool()


# ---------------------------------------------------------------------------
# FastAPI app
# ---------------------------------------------------------------------------


@asynccontextmanager
async def lifespan(_app: FastAPI):
yield
await backend.shutdown()


app = FastAPI(title="Arctic Inference", lifespan=lifespan)


@app.post("/init")
async def init_endpoint(request: InitRequest):
try:
n = await backend.initialize(
request.config, model_id=request.model_id,
num_replicas=request.num_replicas,
)
return {"status": "ready", "model_id": request.model_id, "num_replicas": n}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


@app.post("/generate")
async def generate_endpoint(request: GenerateRequest):
try:
results = await backend.generate(**request.model_dump())
return {"results": results}
except RuntimeError as e:
msg = str(e).lower()
if "paused" in msg or "cancelled" in msg:
raise HTTPException(status_code=503, detail=str(e))
raise HTTPException(status_code=500, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


@app.post("/sleep")
async def sleep_endpoint(request: SleepRequest):
"""Free GPU memory for a model (drain in-flight requests first)."""
try:
return await backend.sleep(request.model_id, level=request.level)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


@app.post("/wake_up")
async def wake_up_endpoint(request: WakeUpRequest):
"""Restore GPU memory for a model and resume serving."""
try:
return await backend.wake_up(request.model_id, tags=request.tags)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


@app.get("/weights_info")
async def weights_info_endpoint(model_id: str | None = None):
try:
infos = backend.get_weights_info(model_id=model_id)
return {"weights_info": infos, "count": len(infos)}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


@app.post("/sync_weights")
async def sync_weights_endpoint(request: SyncWeightsRequest):
try:
return await backend.sync_weights(**request.model_dump())
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


@app.get("/spec_weights_info")
async def spec_weights_info_endpoint(model_id: str | None = None):
try:
infos = backend.get_spec_weights_info(model_id=model_id)
return {"weights_info": infos, "count": len(infos)}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


@app.post("/sync_spec_weights")
async def sync_spec_weights_endpoint(request: SyncSpecWeightsRequest):
try:
return await backend.sync_spec_weights(**request.model_dump())
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


@app.post("/close_weight_sync")
async def close_weight_sync_endpoint(model_id: str | None = None):
try:
return await backend.close_weight_sync(model_id=model_id)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


@app.get("/status")
async def status_endpoint():
try:
return await backend.get_status()
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


@app.get("/metrics")
async def metrics_endpoint(model_id: str | None = None):
"""Drain per-replica snapshots and per-request records.

Each call empties the server-side ring buffers, so consumers see only
metrics produced since their previous call.
"""
try:
return await backend.drain_metrics(model_id=model_id)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


@app.post("/shutdown")
async def shutdown_endpoint(model_id: str | None = None):
try:
await backend.shutdown(model_id=model_id)
return {"status": "shutdown", "model_id": model_id}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
30 changes: 30 additions & 0 deletions arctic_inference/server/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""CLI entry point: ``arctic-inference-server``."""
from __future__ import annotations

import argparse


def main() -> None:
parser = argparse.ArgumentParser(
prog="arctic-inference-server",
description="Launch the Arctic Inference server",
)
parser.add_argument("--host", default="0.0.0.0", help="Bind address (default: 0.0.0.0)")
parser.add_argument("--port", type=int, default=8000, help="Bind port (default: 8000)")
parser.add_argument("--workers", type=int, default=1, help="Number of uvicorn workers (default: 1)")
parser.add_argument("--log-level", default="info", help="Log level (default: info)")
args = parser.parse_args()

import uvicorn

uvicorn.run(
"arctic_inference.server.api:app",
host=args.host,
port=args.port,
workers=args.workers,
log_level=args.log_level,
)


if __name__ == "__main__":
main()
Loading
Loading