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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

This file was deleted.

2 changes: 1 addition & 1 deletion docs/src/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ model_provisioning:
| `enabled` | boolean | `false` | Apply the declarative model catalog during startup. |
| `directory` | path | `/app/model-provisioning.d` | Directory containing one `.yaml` or `.yml` document per canonical model. |
| `org_overlays_directory` | path | `/app/org-overlays.d` | Directory containing one `.yaml` or `.yml` document per organisation with per-model serving overrides (explicit `targets`, `self_hosted_only`). Applied after the model catalog when `enabled` is set. A missing directory is an empty catalog, so a deployment that does not mount one applies no overlays; an existing path that is not a directory fails startup. |
| `account_limits_directory` | path | `/app/account-limits.d` | Directory containing one `.yaml` or `.yml` document per account with its own realtime in-flight limits on virtual models. Applied after the model catalog when `enabled` is set, replacing every stored per-account limit. A missing directory changes nothing; an empty one clears every per-account limit. See [Account limits](./model-provisioning.md#account-limits). |
| `account_limits_directory` | path | `/app/account-limits.d` | Directory containing one `.yaml` or `.yml` document per account with its own realtime in-flight limits, prices, scheduling priority and latency targets on virtual models. Applied after the model catalog and organisation overlays when `enabled` is set; limits replace every stored per-account limit, and the other settings are owned row by row. A missing directory changes nothing; an empty one clears what the files own. See [Account limits](./model-provisioning.md#account-limits). |

When enabled, the directory must exist. Startup fails before any provisioning
writes if loading, validation, or a referenced endpoint/group lookup fails.
Expand Down
85 changes: 70 additions & 15 deletions docs/src/reference/model-provisioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,36 +371,91 @@ leaves partial model edits. Metadata-only edits are still permitted.

## Account limits

An account limit file gives one account its own realtime in-flight limit on
some virtual models, in place of each model's
`settings.realtime_inflight_limit`. The account is a user or an organisation,
named by its username, and an organisation's limit covers every key the
An account file holds one account's own settings on some virtual models: its
realtime in-flight limits, its prices, and the scheduling priority and latency
targets sent with its requests. The account is a user or an organisation,
named by its username, and an organisation's settings cover every key the
organisation owns. One file per account, mounted at
`model_provisioning.account_limits_directory`:

```yaml
account: acme
priority: 100 # optional; default for every model
models:
example/chat-model:
priority: 300 # optional; overrides the account default
tariffs: # batch prices only, at model level
- {name: batch-24h, purpose: batch, completion_window: 24h,
input_per_million_tokens: "0.50", output_per_million_tokens: "1.50"}
classes:
standard:
realtime_inflight: 200
tariffs: # realtime and playground prices, per class
- {name: realtime, purpose: realtime,
input_per_million_tokens: "0.70", output_per_million_tokens: "1.40"}
cache_tariff: {read_multiplier: "0.2", write_multiplier_5m: "1",
write_multiplier_1h: "1", write_multiplier_24h: "1"}
ttft_ms: 1000 # optional latency targets
itl_ms: 30
priority: 500 # optional; overrides the model's priority
```

The older shape is still accepted and means the same as
`classes.standard.realtime_inflight`:

```yaml
account: acme
realtime_inflight:
example/chat-model: 200
example/fast-model: 40
```

Startup applies the files after the model catalog, in one transaction, and
replaces every stored per-account limit with what the files declare. Removing
a line or a file returns that account to the model's default on the next
start. An empty directory clears every per-account limit; a missing directory
changes nothing. An unknown account, or a model that is not a live virtual
model, fails startup before any per-account limit is written; the model catalog
and organisation overlays have already been applied by then.
Rules:

- `classes` may name only `standard` for now. A class the file omits keeps the
model's defaults.
- Limits, realtime and playground prices, and the cache tariff are declared per
class; model-level `tariffs` take batch prices only.
- Priority inherits class, then model, then account, then the default. It is
sent as the scheduling priority on the account's realtime requests to
`dynamo` serving members; batch and flex keep their deadline priority.
`ttft_ms` and `itl_ms` are sent as latency targets on the same realtime
requests. Neither changes the class the request is billed as. Priority
`-2147483648` is reserved for background work and rejected.
- If a model appears in both shapes, the two limits must be equal.
- An account/model pair may be configured in its account file or in an
[organisation overlay](#organisation-catalog-validation-and-ownership), not
both. The older limits shape may sit alongside an overlay.

Startup applies the files after the model catalog and organisation overlays,
in one transaction under one advisory lock:

- **Limits** replace every stored per-account limit with what the files
declare. Removing a line or a file returns that account to the model's
default on the next start.
- **Priorities, latency targets and prices** are owned row by row. Declaring
an account/model pair makes the file authoritative for its current prices
and policies, adopting matching rows written by hand. Omitted prices retire, and changed
prices are versioned without altering historical amounts. Rows that another
writer owns, such as the organisation catalog's prices, are refused, never
taken over.

An empty directory clears everything the account files own. A missing
directory changes nothing. An unknown account, or a model that is not a live
virtual model, fails startup before any account-file setting is written; the
model catalog and organisation overlays have already been applied by then.

Run the same offline validation used by deployment CI:

```sh
dwctl-model-provisioning validate-account-limits ./account-limits.d --models ./model-provisioning.d
dwctl-model-provisioning validate-account-limits ./account-limits.d \
--models ./model-provisioning.d --org-overlays ./org-overlays.d
dwctl-model-provisioning account-schema
```

It rejects unknown fields, limits below 1, an account declared in two files and
models absent from the model catalog. Whether each account exists can only be
It rejects unknown fields and classes, limits and latency targets below 1, an
account declared in two files, entries that configure nothing, prices at the
wrong level, models absent from the model catalog and, with `--org-overlays`,
pairs configured in both catalogs. Whether each account exists can only be
checked against the database, at startup.

`GET /admin/api/v1/models/{id}/realtime-inflight-limits` returns a virtual
Expand Down
39 changes: 39 additions & 0 deletions dwctl/migrations/20261021150000_account_policies.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
-- Per-account serving policy from the account catalog files: an account-wide
-- scheduling priority, and per-model or per-class priority and latency targets.
-- Written only by the account catalog reconciler.
SET LOCAL lock_timeout = '5s';

ALTER TABLE users ADD COLUMN scheduling_priority INTEGER;

COMMENT ON COLUMN users.scheduling_priority IS
'Account setting from the account catalog: the scheduling priority sent with this account''s realtime requests to self-hosted serving members, unless a model or class policy overrides it. NULL sends the default.';

CREATE TRIGGER users_scheduling_priority_notify
AFTER UPDATE OF scheduling_priority ON users
FOR EACH ROW
WHEN (OLD.scheduling_priority IS DISTINCT FROM NEW.scheduling_priority)
EXECUTE FUNCTION notify_config_change();

CREATE TABLE account_model_policies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
deployed_model_id UUID NOT NULL REFERENCES deployed_models(id) ON DELETE CASCADE,
-- NULL applies to every class of the model; a class key narrows it.
serving_class TEXT,
priority INTEGER,
ttft_ms INTEGER CONSTRAINT chk_account_model_policies_ttft CHECK (ttft_ms > 0),
itl_ms INTEGER CONSTRAINT chk_account_model_policies_itl CHECK (itl_ms > 0),
provisioning_source TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_account_model_policies_scope UNIQUE NULLS NOT DISTINCT (user_id, deployed_model_id, serving_class)
);

COMMENT ON TABLE account_model_policies IS
'Per-account serving policy on one model (serving_class NULL) or one of its classes, from the account catalog files.';

CREATE INDEX idx_account_model_policies_model ON account_model_policies (deployed_model_id);

CREATE TRIGGER account_model_policies_notify
AFTER INSERT OR UPDATE OR DELETE ON account_model_policies
EXECUTE FUNCTION notify_config_change();
Loading
Loading