Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

- Minimal. Only where the *why* cannot be read off the code.
- No narrative paragraphs above functions, no package essays, no restating the next line.
- Rationale belongs in `docs/swiss-design.md`, not in source. Code goes stale silently; the doc can be edited.
- Rationale belongs in `docs/architecture.md`, not in source. Code goes stale silently; the doc can be edited.

## Formatting

Expand Down
67 changes: 67 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Development

How the pieces fit: [docs/architecture.md](docs/architecture.md).

## Prerequisites

- Go (version in `go.mod`), helm, node/npm for the UI
- For e2e: docker, kind, kubectl
- The UI source is a submodule: `git submodule update --init`

## Build

```sh
go build -o swiss ./cmd/swiss
go build -o swissd ./cmd/swissd
hack/web.sh # build the UI into web/dist, which swissd embeds
make docker-build # swissd image, IMG=swissd:e2e by default
```

`web/dist` is committed empty, so `go build` works without the UI.

## Run locally

```sh
./swissd --config examples/swiss.yaml
./swiss catalog list
```

Configuration: [README.md](README.md#configure).

## Unit tests

```sh
make test # go test ./...
```

The server tests read [model-catalog](https://github.com/modelsphere/model-catalog) from `../swiss-catalog` and fail without it. Clone it there at the `CATALOG_REF` pinned in `.github/workflows/ci.yml` (CI does the same):

```sh
git clone https://github.com/modelsphere/model-catalog ../swiss-catalog
git -C ../swiss-catalog checkout <CATALOG_REF>
```

## e2e tests (kind)

```sh
make test-e2e # KEEP_CLUSTER=1 leaves the cluster up
make cleanup-test-e2e
```

Details and settings: [docs/e2e.md](docs/e2e.md).

## Lint

What CI runs:

```sh
gofmt -l cmd internal helm test web/embed.go # must print nothing
make vet # go vet, including -tags=e2e
helm lint --strict helm/swiss --set 'rbac.namespaces={ci}'
```

## Pull requests

1. Fork, branch from `master`.
2. Run the unit tests and lint above.
3. Open a PR against `modelsphere/swiss` `master`. CI (`ci.yml`) must pass; `e2e.yml` runs too but is not required.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
[![stars](https://img.shields.io/github/stars/modelsphere/swiss?style=flat&logo=github)](https://github.com/modelsphere/swiss/stargazers)

A deploy control plane for the sglang and vllm charts. Design and rationale:
[`docs/swiss-design.md`](docs/swiss-design.md).
[`docs/architecture.md`](docs/architecture.md). Building and testing: [`DEVELOPMENT.md`](DEVELOPMENT.md).

## Configure

Expand Down Expand Up @@ -68,7 +68,7 @@ PUT /api/profile
POST /api/plans /api/diff /api/apply /api/install (allowDeploy only)
```

`applyWith` on the site profile chooses the backend for a new install: `helm` (the default when the field is omitted) or `llmsvc` when the LLMService CRD is served. A release already in the cluster stays on the backend that owns it. `llmsvc` requires the plan's `source.schema` to be v2; empty means v1 and is rejected with 400 and nothing is written. Helm accepts v1 and v2. The CLI `swiss install` and `swiss apply` stay on helm. See [docs/swiss-design.md](docs/swiss-design.md).
`applyWith` on the site profile chooses the backend for a new install: `helm` (the default when the field is omitted) or `llmsvc` when the LLMService CRD is served. A release already in the cluster stays on the backend that owns it. `llmsvc` requires the plan's `source.schema` to be v2; empty means v1 and is rejected with 400 and nothing is written. Helm accepts v1 and v2. The CLI `swiss install` and `swiss apply` stay on helm. See [docs/architecture.md](docs/architecture.md).

## Deploy

Expand Down
72 changes: 72 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Swiss architecture

Swiss is a deploy control plane for the sglang and vllm charts. It composes a
model release from a public catalog, a site profile and deploy-time settings,
then hands it to helm. It feeds the existing charts, `helmfile.yaml` and the
`Makefile` targets rather than replacing them.

- **Built:** the catalog, `swiss` (CLI), `swissd` (HTTP server with an embedded
web UI, write path behind `server.allowDeploy`).
- **Not built:** preflight, `emit`, auth.

## Components

| piece | artifact | runtime |
| --- | --- | --- |
| **swiss-catalog** | published static files, data only | none |
| **core** | Go module, `internal/` | library |
| **`swiss`** | binary | CLI, stateless |
| **`swissd`** | binary | HTTP server |
| **web UI** | React + TypeScript | embedded into `swissd` |

Three things are deployed: the catalog, `swissd`, and the engine charts in the
helm repo (`https://modelsphere.github.io/helm-charts` by default). The catalog
is its own project: [model-catalog](https://github.com/modelsphere/model-catalog).

```mermaid
flowchart LR
catalog[(swiss-catalog)] --> core
profile[(site profile)] --> core
cli[swiss CLI] --> core
ui[web UI] --> swissd --> core
core[core: compose, diff, apply] --> helm[helm / helmfile]
core --> llmsvc[LLMService]
helm --> charts[sglang / vllm charts]
```

## Layers

Composition is a few inputs with key sets and strict precedence. Later layer overrides.

| layer | owns |
| --- | --- |
| helm chart defaults | things defaults in sglang/vllm chart values.yaml |
| **site defaults** (`defaults`) | the site's preferences, config it in `defaults.features` in the profile, the `cart`, `serviceMonitor`, `sloRequirement` and `scaler` `enabled` flags, unset on |
| **catalog variant** | model deployment recipes define in [model-catalog](https://github.com/modelsphere/model-catalog) and how it parallelizes: `model.*`, `extraArgs`, `lws.*`, `env`, `image.repository` |
| **site profile** | what makes it work here: the registry mirror, cache paths, route ConfigMaps, namespace, the `model.localPath` template, the default scheduler and priority class |
| **derived** | swiss's rules, above the variant: a section the form filled in is switched on, `cache.maxSlotsPerNode` |
| **user form** | how much, where, how routed: `serviceId`, `replicaCount`, `scaler.*`, `modelRoute.*`, scheduling |
| **plan editor** | the escape hatch, applied last — any key, exempt from ownership, labelled `edit` |

## Pipeline

```
catalog entry ─┐
site profile ─┼─► compose ─► Plan ─► diff ─► apply | install ─► helm
form / flags ─┘ (hash) ↓
revision lock
```

`applyWith` on the site profile picks the backend for a new install: `helm` (default) or `llmsvc` (LLMService). A release already in the cluster stays on the backend that owns it.

| endpoint | |
| --- | --- |
| `POST /api/plans` | compose; returns the plan and stores it by hash |
| `POST /api/diff` | diff a stored plan against the live release |
| `POST /api/apply` | upgrade, asserting the diffed revision when one is supplied |
| `POST /api/install` | create |
| `DELETE /api/releases/{ns}/{release}` | uninstall |
| `POST /api/releases/{ns}/{release}/chat` | health check: one real inference request |
| `GET /api/runs`, `GET /api/runs/{id}` | the operation log; output is per row, not in the list |
| `GET /api/nodes` | the GPU inventory: type, allocatable, in use, and what holds it |
| `GET /api/profile` | the site profile as parsed, after defaults |
120 changes: 0 additions & 120 deletions docs/swiss-design.md

This file was deleted.

Loading