Compare commits

..

123 Commits

Author SHA1 Message Date
Ryuichi Leo Takashige 1350a409ff CUDA TYPINGS 2026-03-12 16:25:48 +00:00
Mustafa Alp Yılmaz ea18a62581 fix: guard against ZeroDivisionError in mlx_lm stats (#1707)
## Problem

Running short completions (like `max_tokens=1` health check probes) can
finish so fast that `mlx_lm`'s internal `generation_time` rounds to
zero. When that happens, `BatchGenerator.stats()` in
`mlx_lm/generate.py` divides `generation_tokens / generation_time` and
throws a `ZeroDivisionError`, which kills the runner process.

EXO already handles this on its side — lines 289-293 in
`batch_generate.py` guard the TPS calculation with `if
generation_elapsed > 0`. But the call to `self._exo_gen.stats()` on line
294 goes into mlx_lm's *separate* timing code, which doesn't have the
same guard. Two different timers, only one is protected.

In my case, this was triggered by health check probes (content: `"a"`,
`max_tokens=1`). The generation completed in sub-microsecond time,
`generation_time` was exactly `0`, and the runner crashed. Since the
health check command stays in the queue and retries after recovery, it
created an infinite crash loop — every ~15 seconds the runner would load
the model, get the same health check, and die again.

## Fix

Wrap `self._exo_gen.stats()` in a `try/except ZeroDivisionError`. If it
throws, set `mlx_stats` to `None` and fall back to `0.0` for
`prompt_tps`. The only fields EXO reads from `mlx_stats` are
`prompt_tps` and `prompt_time` — losing them on a sub-microsecond
generation has no practical impact.

## Traceback

```
File "batch_generate.py", line 294, in step
    mlx_stats = self._exo_gen.stats()
File "mlx_lm/generate.py", line 1224, in stats
    self._stats.generation_tokens / self._stats.generation_time
ZeroDivisionError: division by zero
```
2026-03-12 14:48:36 +00:00
Miguel Cruz 0782d90ec5 fix: show partial download progress on initial dashboard load (#1706)
## Summary

- Dashboard now shows partial download progress for models that were
partially downloaded in a previous session, instead of showing 0%
- Both `getModelDownloadStatus()` and `getInstanceDownloadStatus()` now
handle `DownloadPending` entries that carry non-zero
`downloaded`/`total` bytes

Fixes #1042

## Root cause

When exo restarts with partially downloaded models, the
`DownloadCoordinator` emits `DownloadPending` events (because
`downloaded_this_session` is 0, even though real bytes exist on disk).
The main page dashboard only checked for `DownloadOngoing` entries, so
these partially downloaded models showed as 0%.

The dedicated `/downloads` page already handled this correctly — it
renders `DownloadPending` entries with a progress bar when `downloaded >
0`. This fix brings the same behavior to the main page.

## Changes

For both `getModelDownloadStatus()` and `getInstanceDownloadStatus()` in
`+page.svelte`:
- Accept `DownloadPending` in addition to `DownloadOngoing`
- For `DownloadPending` entries with `downloaded > 0` or `total > 0`,
synthesize a `DownloadProgress` object from the top-level fields (with
`speed: 0` and `etaMs: 0` since no active download is in progress)
- Skip `DownloadPending` entries where both `downloaded` and `total` are
0 (truly pending, not yet started)

## Test plan

- [ ] Partially download a model, quit exo, relaunch — dashboard should
show partial progress instead of 0%
- [ ] Fully downloaded models still show as complete
- [ ] Active downloads still show real-time progress with speed/ETA
- [ ] Models never downloaded show as not started (not falsely showing
progress)
- [ ] Dashboard builds without errors (`cd dashboard && npm run build`)

---------

Co-authored-by: Evan <evanev7@gmail.com>
2026-03-12 10:39:34 +00:00
rltakashige f221a6c85c Normalise Responses API tool call format (#1704)
## Motivation

The responses API often does not provide tool args nested under a
"function" field. Since we follow the chat completions format of tools
in the backend (for MLX chat templates), we need to normalise to this
format.

## Test Plan

### Manual Testing
Works on n8n!
<img width="3442" height="2076" alt="image"
src="https://github.com/user-attachments/assets/9e11d679-0102-4d83-9a8e-b0a7a5898708"
/>
2026-03-11 18:10:12 +00:00
Mustafa Alp Yılmaz 2994b41089 fix: validate num_key_value_heads in tensor sharding placement (#1669)
## Problem

Models with fewer KV heads than nodes crash during tensor parallelism.
For example, Qwen3.5 MoE models have only 2 KV heads — trying to shard
across 4 nodes produces empty tensors and a reshape error at runtime.

The placement system already validates `hidden_size % num_nodes == 0`
but doesn't check KV heads, so it creates configurations that look valid
but blow up when the worker tries to split the attention heads.

Affected models include Qwen3.5-35B-A3B, Qwen3.5-122B-A10B,
Qwen3.5-397B-A17B, Qwen3-Next-80B-A3B, and Qwen3-Coder-Next (all have 2
KV heads).

## Changes

**Placement validation** (`src/exo/master/placement.py`):
- Combined KV heads divisibility check with the existing hidden_size
filter in a single pass
- Cycles where `num_key_value_heads % len(cycle) != 0` are now excluded
for tensor sharding
- Error message includes both constraints when no valid cycle is found

**Model card schema** (`src/exo/shared/models/model_cards.py`):
- Added optional `num_key_value_heads` field to `ModelCard` and
`ConfigData`
- Extracted from HuggingFace `config.json` (handles both top-level and
`text_config` nesting)
- Passed through in `fetch_from_hf()` for dynamically fetched cards

**All 68 inference model cards**
(`resources/inference_model_cards/*.toml`):
- Populated `num_key_value_heads` from each model's HuggingFace config

**Utility script** (`scripts/fetch_kv_heads.py`):
- Fetches `num_key_value_heads` from HuggingFace and updates TOML cards
- `--missing`: only fills in cards that don't have the field yet
- `--all`: re-fetches and overwrites everything
- Uses tomlkit for safe TOML editing and ThreadPoolExecutor for parallel
fetches

## Behavior

- Instance previews no longer show tensor options for models that can't
split their KV heads across the cluster size
- `place_instance()` rejects with a clear error instead of crash-looping
- Pipeline parallelism is unaffected
- 2-node tensor still works for 2-KV-head models (2 ÷ 2 = 1)
- Field is optional — existing custom cards without it continue to work
(validation is skipped when `None`)
2026-03-11 13:46:33 +00:00
Mustafa Alp Yılmaz 38f0c09175 fix: use StreamingDetokenizer in batch generator to fix emoji/UTF-8 corruption (#1691)
## Problem

Emojis and other multi-byte UTF-8 characters are rendered as `\ufffd`
(Unicode Replacement Character) in batch streaming responses.

Byte-level BPE tokenizers (like Qwen's) can split multi-byte UTF-8
characters (e.g. a 4-byte emoji) across multiple tokens. The batch
generator decodes each token independently with
`tokenizer.decode([token_id])`, which produces U+FFFD for partial byte
sequences.

The sequential generator doesn't have this problem — it uses
`stream_generate()` from mlx_lm, which internally uses
`StreamingDetokenizer` to buffer incomplete bytes.

## Fix

Use `StreamingDetokenizer` in the batch generator, matching the
sequential path:

- Each `_EngineTask` gets its own `StreamingDetokenizer` instance
- `add_token()` buffers tokens, holding back incomplete UTF-8 byte
sequences until they form valid characters
- `last_segment` returns only complete, valid text
- `finalize()` flushes any remaining buffered bytes when generation
completes

Empty text segments during buffering are harmless — they're already
handled correctly by the downstream streaming pipeline.

---------

Co-authored-by: Ryuichi Leo Takashige <leo@exolabs.net>
2026-03-10 16:10:22 +00:00
ciaranbor f36fd56c38 Include power usage in bench responses (#1692)
## Motivation

Add power/energy usage tracking to /bench API responses.

## Changes

- New PowerSampler class that periodically samples sys_power from each
node during inference
- New PowerUsage / NodePowerStats API types
- Integrated into both bench chat completion and bench image generation
endpoints

## Why It Works

Runs concurrently via anyio.create_task_group, reads from existing
state.node_system heartbeat data — no new plumbing needed. Energy =
avg_power × elapsed_time.

## Test Plan

### Manual Testing

Run a /bench request and verify power_usage appears in the response.

### Automated Testing

7 async tests covering single/multi-node, energy math, dynamic power
changes, empty state, and lifecycle.
2026-03-10 16:02:55 +00:00
rltakashige 82c54dd6d6 Add support for Nemotron sharding (#1693)
### Automated Testing
tested logits match
2026-03-10 15:51:07 +00:00
ciaranbor 3536161f15 Fix stale state.runners (#1684)
## Motivation

When a runner shuts down, there's no mechanism to remove it from
state.runners, causing stale state to accumulate.

## Changes

- apply.py: apply_runner_status_updated now handles RunnerShutdown
status by removing the runner from state instead of adding/updating it.
Removed the separate apply_runner_deleted function entirely.
- events.py: Removed the RunnerDeleted event type — runner cleanup is
now handled via RunnerStatusUpdated with a RunnerShutdown status.
- Tests: New test_apply_runner_deleted.py with 2 tests: verifying
RunnerShutdown removes the runner, and that a normal status update adds
it.

## Why It Works

Instead of a separate RunnerDeleted event and coordinating its emission
from master/placement, runner cleanup is handled naturally through the
existing RunnerStatusUpdated path — a RunnerShutdown status signals
removal. This is simpler and requires no changes to master or placement
logic.

## Test Plan

### Automated Testing

2 new unit tests covering RunnerShutdown removal and normal runner
status addition
2026-03-10 09:32:25 +00:00
ArvidSU a6aa07ed83 feat(dashboard): add mobile drawer support for sidebars (#1677)
## Summary

- Adds mobile-friendly drawer UI for the chat sidebar and model family
sidebar
- Introduces responsive header navigation with mobile-specific toggle
buttons
- Uses slide-in drawer overlay pattern on small screens instead of
collapsible sidebars
- Stores mobile drawer state in app store for consistent state
management

## Testing

Tested on desktop (responsive mode) and mobile viewport widths. Sidebars
now slide in as drawers on small screens with proper z-index layering
and close-on-outside-click behavior.
2026-03-10 04:18:11 +00:00
rltakashige 131ad0ff36 Implement continuous batching (#1642)
## Motivation

Following the changes made in #1632 !
Closes #1020

## Changes

<!-- Describe what you changed in detail -->

## Why It Works

<!-- Explain why your approach solves the problem -->

## Test Plan

### Manual Testing
<!-- Hardware: (e.g., MacBook Pro M1 Max 32GB, Mac Mini M2 16GB,
connected via Thunderbolt 4) -->
<!-- What you did: -->
<!-- - -->

### Automated Testing
<!-- Describe changes to automated tests, or how existing tests cover
this change -->
<!-- - -->

---------

Co-authored-by: Evan Quiney <evanev7@gmail.com>
2026-03-09 15:04:45 +00:00
ciaranbor d01636100a Ciaran/gpt oss tool call finish reason (#1673)
## Motivation

When gpt-oss truncates a response mid-tool-call (e.g. hitting max
tokens), the finish_reason was swallowed because the parser was in
tool-accumulation mode and never yielded it back to the caller. This
caused the API to hang or fail to signal completion.

## Changes

In parse_gpt_oss, when accumulating tool call arguments and a
finish_reason arrives, yield the response (with empty text) so the
finish reason propagates

## Why It Works

The parser now checks for finish_reason on every chunk while inside a
tool call. When the model stops generating (truncation), the finish
signal is forwarded immediately rather than being silently consumed.

## Test Plan

### Automated Testing

Added tests covering truncated tool calls (finish_reason="length") and
plain text truncation
2026-03-06 21:09:20 +00:00
ciaranbor 79c8dbeacd Ciaran/minor download bugs (#1674)
## Motivation

Two minor bugs in the download coordinator: cancelling a download didn't
reset its status to pending, and a single error in
_emit_existing_download_progress would kill the loop permanently.

## Changes

- Cancel emits pending status: When a download is cancelled, emit a
DownloadPending event so the UI/cluster state reflects that the model is
no longer actively downloading.
- Resilient progress emission loop: Move the try/except inside the while
loop so transient errors are logged but the periodic emission continues.

## Why It Works

- The cancel fix ensures the download status is consistent — without it,
a cancelled download would still appear as in-progress.
- The loop fix prevents a single exception from permanently stopping the
60-second periodic progress broadcast.
2026-03-06 17:46:18 +00:00
Evan Quiney 0096159728 up gossipsub limit (#1671)
bump gossipsub limit to 8MB + add warning
this is a bandaid solution for large messages while we figure out the
point to point protocol
2026-03-06 14:20:51 +00:00
Andrei Onel 7a36d3968d docs: Update documentation for v1.0.68 release (#1667)
## Motivation

Updated documentation for v1.0.68 release

## Changes

**docs/api.md:**
- Added documentation for new API endpoints: Claude Messages API
(`/v1/messages`), OpenAI Responses API (`/v1/responses`), and Ollama API
compatibility endpoints
- Documented custom model management endpoints (`POST /models/add`,
`DELETE /models/custom/{model_id}`)
- Added `enable_thinking` parameter documentation for thinking-capable
models (DeepSeek V3.1, Qwen3, GLM-4.7)
- Documented usage statistics in responses (prompt_tokens,
completion_tokens, total_tokens)
- Added streaming event format documentation for all API types
- Updated image generation section with FLUX.1-Kontext-dev support and
new dimensions (1024x1365, 1365x1024)
- Added request cancellation documentation
- Updated complete endpoint summary with all new endpoints
- Added security notes about trust_remote_code being opt-in

**README.md:**
- Updated Features section to highlight multiple API compatibility
options
- Added Environment Variables section documenting all configuration
options (EXO_MODELS_PATH, EXO_OFFLINE, EXO_ENABLE_IMAGE_MODELS,
EXO_LIBP2P_NAMESPACE, EXO_FAST_SYNCH, EXO_TRACING_ENABLED)
- Expanded "Using the API" section with examples for Claude Messages
API, OpenAI Responses API, and Ollama API
- Added custom model loading documentation with security notes
- Updated file locations to include log files and custom model cards
paths

**CONTRIBUTING.md:** 
- Added documentation for TOML model cards format and the API adapter
pattern

**docs/architecture.md:**
- Documented the adapter architecture introduced in PR #1167

Closes #1653

---------

Co-authored-by: askmanu[bot] <192355599+askmanu[bot]@users.noreply.github.com>
Co-authored-by: Evan Quiney <evanev7@gmail.com>
2026-03-06 11:32:46 +00:00
Mustafa Alp Yılmaz eee3432738 feat(mlx): add repetition_penalty and repetition_context_size to chat completions (#1665)
## Problem

Models running through exo can get stuck in repetition loops —
generating the same text over and over until hitting the token limit. It
happens more with quantized models where probability distributions can
become degenerate.

`mlx_lm` has `make_logits_processors(repetition_penalty,
repetition_context_size)` that handles this, but it is never called
anywhere in the pipeline.

## Solution

Wire `repetition_penalty` and `repetition_context_size` through the
stack:

- `ChatCompletionRequest` → accept the params from the client
- `TextGenerationTaskParams` → carry them through the pipeline
- `chat_completions.py` adapter → map to internal params
- `generate.py` → call `make_logits_processors()` and merge into the
`logits_processors` list

When `repetition_penalty` is `None` (default), `make_logits_processors`
returns `[]` so existing behavior is unchanged.

## Usage

```json
{
  "model": "mlx-community/...",
  "messages": [...],
  "repetition_penalty": 1.15,
  "repetition_context_size": 64
}
```
2026-03-06 10:51:25 +00:00
Alex Cheema e8c3a873a6 feat(dashboard): improve model picker feedback and HuggingFace search (#1661)
## Motivation

Fixes #1648. Users adding custom models from HuggingFace got no clear
feedback that the model was successfully added — the model didn't appear
prominently in the list. Additionally, searches were limited to
`mlx-community` models with no fallback.

## Changes

- **Success feedback**: After adding a custom model, a toast
notification appears, the view auto-switches to "All Models", and the
newly added model is scrolled into view with a green highlight that
fades over 4 seconds.
- **HuggingFace search fallback**: The `/models/search` endpoint now
searches `mlx-community` first; if no results are found, it falls back
to searching all of HuggingFace.
- **Inline HF results**: When the main search bar finds no local
matches, HuggingFace search results appear inline with "+ Add" buttons
and a "See all results on Hub" link.
- **Full repo ID for non-mlx models**: Non-mlx-community models now
display the full repo ID (e.g., `meta-llama/Llama-3.1-8B`) instead of
just the short name.

## Why It Works

The toast + scroll + highlight gives immediate, unambiguous feedback
that the model was added and where it lives in the list. The HF search
fallback broadens discoverability while still prioritising mlx-community
models. Inline results in the main search bar mean users don't need to
navigate to the HF tab to discover new models.

## Test Plan

### Manual Testing
- Open model picker → HuggingFace Hub tab → search for a model → click
"+ Add"
- Verify: toast appears, view switches to All Models, model highlighted
with green glow
- Search for a term with no mlx-community results → verify fallback to
full HF search
- Non-mlx-community results show full repo ID (e.g., `org/model-name`)
- On All Models tab, search for a model not in the local list → verify
"From HuggingFace" section appears

### Automated Testing
- Existing tests cover the model catalog and API; no new automated tests
needed for UI behaviour changes

---------

Co-authored-by: Evan <evanev7@gmail.com>
2026-03-06 10:23:08 +00:00
Michael Harrigan afab3095b0 fix: KVPrefixCache Regression (#1668) 2026-03-06 10:11:08 +00:00
ciaranbor b9d40e8e35 Ciaran/re download bug (#1658)
## Motivation

After deleting a model and re-downloading it, the CachedShardDownloader
returns the stale cached path, so ensure_shard short-circuits and no
download actually happens.

## Changes

- Added invalidate(model_id) method to the ShardDownloader ABC and all
implementations
- CachedShardDownloader.invalidate evicts cache entries matching the
model ID and delegates down
- DownloadCoordinator.delete_model calls invalidate after cancelling
active downloads, before deleting files
- Added end-to-end test that downloads, deletes, and re-downloads a
model through the coordinator

## Why It Works

The cache is cleared when a model is deleted, so the next ensure_shard
call performs a fresh download instead of returning the stale path.

## Test Plan

## Automated Testing

New test_re_download_after_delete_completes exercises the full download
→ delete → re-download flow through DownloadCoordinator with
CachedShardDownloader + SingletonShardDownloader wrappers matching
production.
2026-03-05 14:18:17 +00:00
wysie 3a4d635d0c Fix copy code button not working in dashboard (#1659)
Fixes #1657

## Motivation

The copy button on code blocks in the dashboard does nothing when
clicked. This affects all code blocks in assistant responses.

## Root Cause

In `MarkdownContent.svelte`, click event listeners are bound to
`.copy-code-btn` elements via `setupCopyButtons()` inside a Svelte 5
`$effect`. However, the effect fires before the DOM has been updated
with the new HTML, so `querySelectorAll(".copy-code-btn")` finds zero
buttons.

Additionally, during streaming, the `content` prop updates on every
token, causing the entire `{@html processedHtml}` to be re-rendered.
This destroys all previously bound event listeners, even if they were
successfully attached.

## Changes

Replaced the per-button `addEventListener` approach with **event
delegation** — a single click listener on the container element that
catches clicks bubbling up from any `.copy-code-btn` or
`.copy-math-btn`. This:

- Eliminates the timing issue (the listener exists before the buttons
are rendered)
- Survives HTML re-renders during streaming (no need to re-bind)
- Removes the need for `setupCopyButtons()` and the `data-listenerBound`
tracking

## Testing

1. Load any model
2. Prompt it to generate a code block (e.g. "write a hello world in
Python")
3. Click the copy button on the code block
4. Paste — the code is copied correctly
5. Verified the button also works during streaming (before generation
completes)

Co-authored-by: Wysie <wysie@users.noreply.github.com>
2026-03-05 12:41:17 +00:00
Miguel Miranda Dias 8485805042 fix(worker): emit error chunks when a runner dies mid-command (#1645)
Closes #1586

## Summary
- track in-flight tasks in `RunnerSupervisor` (not only unacknowledged
pending tasks)
- when `_check_runner()` detects a crashed runner, emit
`ChunkGenerated(ErrorChunk)` for each in-flight command task
(`TextGeneration`, `ImageGeneration`, `ImageEdits`)
- keep existing `RunnerStatusUpdated(RunnerFailed)` emission so
planner/state still transition correctly
- add a unit test for supervisor crash path to ensure an error chunk is
emitted before failed runner status

## Why
`#1586` reports streams that can hang forever when runners crash during
warmup/loading. This keeps failure signaling at the runner-supervisor
layer, matching maintainer guidance in the issue thread.

## Validation
- attempted: `uv run pytest
src/exo/worker/tests/unittests/test_runner/test_runner_supervisor.py`
- blocked locally by environment disk exhaustion while uv tried to
materialize heavy CUDA wheels (`No space left on device` during
`nvidia-cudnn-cu13` extraction)

I kept the change scoped and added a targeted unit test for the failure
path.

---------

Co-authored-by: Evan <evanev7@gmail.com>
2026-03-04 17:15:58 +00:00
ciaranbor 4de8f801c7 #Add reasoning parms to chat completion and responses APIs (#1654)
## Motivation

Adds reasoning_effort parameter support to both the Chat Completions and
Responses APIs, aligning with the OpenAI spec and enabling thinking
control for gpt-oss models

## Changes

- Added ReasoningEffort literal type ("none" | "minimal" | "low" |
"medium" | "high" | "xhigh") and a resolve_reasoning_params() helper
that cross-derives reasoning_effort ↔ enable_thinking when only one is
provided
- Added reasoning_effort field to ChatCompletionRequest and reasoning
(with Reasoning model) + enable_thinking to ResponsesRequest
- Both adapters now call resolve_reasoning_params() before building
TextGenerationTaskParams
- reasoning_effort is passed through to the MLX chat template as a
template variable

## Why It Works

resolve_reasoning_params is a pure function that normalises the two
overlapping knobs (reasoning_effort and enable_thinking) into a
consistent pair, so downstream code always has both values regardless of
which the caller supplied.

## Test Plan

### Automated Testing

Added test_resolve_reasoning_params.py with 10 test cases covering:
both-None, both-set passthrough, enable_thinking → effort derivation,
and effort → enable_thinking derivation for every ReasoningEffort
variant.
2026-03-04 14:27:49 +00:00
Owleksiy 5777bf3c39 fix: coerce tool-call argument types from tool schema (#1651)
Apply schema-aware coercion to parsed tool-call arguments so
Hermes-style toolcalls can still return typed JSON (e.g. integer ids).

 - pass request tools into parse_tool_calls
 - coerce parsed argument values by function parameters schema
 - add unit tests for coercion and unknown-tool passthrough

## Motivation

Models that use Hermes-based toolcall syntax (Qwen3.5) can't reliably
call tools with non-string parameters
Example tool:
```json
    {
      "type": "function",
      "function": {
        "name": "process",
        "description": "Manage background processes",
        "parameters": {
          "type": "object",
          "properties": {
            "action": {
              "type": "string",
              "enum": ["spawn", "output", "kill", "list"]
            },
            "id": {
              "type": "integer",
              "description": "Process id"
            },
            "command": {
              "type": "string",
              "description": "Command to run for spawn"
            }
          },
          "required": ["action"],
          "additionalProperties": false
        }
      }
    }
```
Model transcript:
```
<tool_call>
<function=process>
<parameter=action>
output
</parameter>
<parameter=id>
0
</parameter>
</function>
</tool_call>
```
And the API returns:

`{"id":"a8f11689-d840-4ca5-ab1d-ead3678a11a9","name":"process","arguments":"{\"action\":
\"output\", \"id\": \"0\"}"}}`

Tool definition declared `id` as `integer`, the model output is
type-agnostic, and the translation layer treats everything as a string.

The same Qwen3.5-27B on OpenRouter and GPT-4.1-mini on openai obey the
function signature and emit correct call:
```
{"name":"process","arguments":"{\"action\": \"output\", \"id\": 0}"}}
```

Steps to reproduce:
```
❯ curl -sS -v http://localhost:52415/v1/chat/completions \
  -H 'Content-Type: application/json' \  -d @- <<'JSON'
{
  "model": "mlx-community/Qwen3.5-27B-4bit",
  "stream": false,
  "temperature": 0,
  "messages": [
    {
      "role": "user",
      "content": "Call the process tool with action=output and id=0. Do not explain anything. Just make the tool call."
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "process",
        "description": "Manage background processes",
        "parameters": {
          "type": "object",
          "properties": {
            "action": {
              "type": "string",
              "enum": ["spawn", "output", "kill", "list"]
            },
            "id": {
              "type": "integer",
              "description": "Process id"
            },
            "command": {
              "type": "string",
              "description": "Command to run for spawn"
            }
          },
          "required": ["action"],
          "additionalProperties": false
        }
      }
    }
  ],
  "tool_choice": {
    "type": "function",
    "function": {
      "name": "process"
    }
  }
}
JSON
```
Look for type of `id` function call

## Changes

Function call parameters are now converted to the types that the
function declaration has

## Why It Works

We now explicitly convert types where we know it (and skip if we don't)

## Test Plan

### Manual Testing
Create Qwen3.5-<ANY> instance, send the curl command above. Check that
'id' is now serialized as a number

### Automated Testing
Unit tests to cover basic type conversions

---------

Co-authored-by: Evan <evanev7@gmail.com>
2026-03-04 12:22:53 +00:00
Evan Quiney 886192f1e6 ignore closed resource errors when trying to cancel a task (#1652)
fixes an occasional crash during the shutdown of a failed instance.
2026-03-03 16:48:43 +00:00
Evan Quiney d914acd64e check if we have a task before we delete it (#1634)
caused a crash we should instead be logging
2026-03-03 15:32:12 +00:00
rltakashige 37296c8249 Refactor runner for implementing batching (#1632)
## Motivation

Batching will require us to send tasks concurrently and queue them up.
Our current infrastructure cannot handle that all. This PR gets us
closer to this by allowing multiple tasks to be sent in parallel and
then queuing up tasks.

## Changes

Change Plan logic
Make runner main into a class
Add a "BatchGenerator" to which tasks can be submitted (although tasks
are handled sequentially) and sent back through an MpSender.
Refactor runner to accept tasks during generation
Keep the generator threading
Separate the runner into several files for better readability

## Test Plan

### Manual Testing
Tested manually, needs a lot more automated testing. Cancellation still
works on a single device. Needs checking on multiple devices.

### Automated Testing

---------

Co-authored-by: Evan Quiney <evanev7@gmail.com>
2026-03-03 14:38:55 +00:00
Daiz 28817d3ee3 Add support for Qwen3.5 (#1644)
## Motivation

Qwen3.5 MoE models (e.g., `Qwen3.5-397B-A17B-6bit`) are now supported by
`mlx-lm` via `qwen3_5_moe` model type, but exo lacks tensor parallel
sharding support for this architecture. This prevents running large
Qwen3.5 models across multiple nodes.

Qwen3.5 uses a GatedDeltaNet hybrid attention mechanism similar to
Qwen3-Next, but with a different projection layout — separate
`in_proj_qkv`, `in_proj_z`, `in_proj_b`, `in_proj_a` instead of
Qwen3-Next's combined `in_proj_qkvz` and `in_proj_ba`. This requires
architecture-aware sharding logic.

## Changes (evan summary)

- enable qwen3_5 dense + moe tensor parallelism from config
- defensively skip evalling _cache.keys if it doesn't exist
- ignore kwargs in qwen35 pipeline masking and ensure pipeline segments match global model parameters for mask creation
- add sharding for qwen3_5 moe linear attention
- added another 6 million model cards

## Why It Works

Qwen3.5's GatedDeltaNet has an `in_proj_qkv` linear layer with three
concatenated sections: `[q(key_dim), k(key_dim), v(value_dim)]`. A naive
contiguous split (`segments=1`) would slice across section boundaries,
corrupting q/k/v values and producing garbled output.

By passing `segments=[key_dim, key_dim + key_dim]` to `shard_linear()`,
each section is split independently before distributing across devices.
This ensures every rank receives correctly aligned q, k, and v
components.

The remaining separate projections (`in_proj_z`, `in_proj_b`,
`in_proj_a`) and the MoE layers follow the same `all_to_sharded` /
`sharded_to_all` pattern already used for Qwen3-Next.

Some pipeline splits didn't include an ssm layer or a linear layer resulting in a subset of the model acting like it shouldn't create the appropriate masks for the next layer - we patch the model to manually create such masks.

## Test Plan

tensor sharded 2,3,4 models & pipeline sharded 2,3,4 with simple eval.

---------

Co-authored-by: hw <hw@hwStudio1.local>
Co-authored-by: Ryuichi Leo Takashige <leo@exolabs.net>
Co-authored-by: Evan <evanev7@gmail.com>
2026-03-03 14:31:57 +00:00
Alex Cheema 0e1b9501c3 fix: mini topology sidebar navigates home on click (#1616)
## Motivation

Clicking on the mini topology view in the right sidebar during chat was
selecting/filtering nodes instead of navigating back to the home view.
The entire mini topology section is wrapped in a button that calls
`handleGoHome()`, but individual node clicks were intercepting the event
via `stopPropagation()`.

## Changes

- Removed `onNodeClick={togglePreviewNodeFilter}` from the mini
sidebar's `TopologyGraph` component
- Added `pointer-events-none` to the topology graph container so all
clicks pass through to the parent button

## Why It Works

`TopologyGraph` only registers click/hover handlers when `onNodeClick`
is provided. Removing it disables node interaction entirely in the mini
view. The `pointer-events-none` class ensures no SVG element can
intercept clicks — they all bubble up to the parent `<button
onclick={handleGoHome}>`, which resets the chat state and returns to the
main topology view.

## Test Plan

### Manual Testing
<!-- Hardware: (e.g., MacBook Pro M1 Max 32GB, Mac Mini M2 16GB,
connected via Thunderbolt 4) -->
<!-- What you did: -->
- Start exo and open the dashboard
- Start a chat to trigger the mini topology sidebar
- Click on the mini topology view → should navigate back to home
- Confirm nodes in the mini topology are not selectable or hoverable

### Automated Testing
- Dashboard builds successfully (`npm run build`)
2026-03-03 10:46:52 +00:00
Mustafa Alp Yılmaz f0d4ccbeb3 feat: add POST /v1/cancel/{command_id} endpoint (#1579)
## Summary

- Adds explicit `POST /v1/cancel/{command_id}` REST endpoint to cancel
in-flight text and image generation commands by ID
- Previously, cancellation only worked via HTTP disconnect (client
closes SSE connection, triggering `anyio.get_cancelled_exc_class()`).
Non-streaming clients and external consumers had no way to cleanly
cancel active generation
- The endpoint looks up the command in active generation queues, sends
`TaskCancelled` to notify workers, and closes the sender stream. Returns
404 (OpenAI error format) if the command is not found or already
completed

## Changes

| File | Change |
|------|--------|
| `src/exo/shared/types/api.py` | Add `CancelCommandResponse` model |
| `src/exo/master/api.py` | Import, route registration, `cancel_command`
handler |
| `src/exo/master/tests/test_cancel_command.py` | 3 test cases (404,
text cancel, image cancel) |
| `docs/api.md` | Document new endpoint + update summary table |

## Design Decisions

- Uses `self._send()` (not raw `command_sender.send()`) — respects API
pause state during elections
- Uses `raise HTTPException` — feeds into exo's centralized OpenAI-style
error handler
- Returns typed `CancelCommandResponse` — consistent with
`CreateInstanceResponse` / `DeleteInstanceResponse` patterns
- `sender.close()` is idempotent so concurrent cancel requests for the
same command are harmless

## Test Plan

- [x] `test_cancel_nonexistent_command_returns_404` — verifies 404 with
OpenAI error format
- [x] `test_cancel_active_text_generation` — verifies 200,
`sender.close()` called, `TaskCancelled` sent with correct
`cancelled_command_id`
- [x] `test_cancel_active_image_generation` — same verification for
image queue
- [x] `basedpyright` — 0 new errors
- [x] `ruff check` — all checks passed
- [x] `pytest` — 3/3 new tests pass, no regressions

---------

Co-authored-by: Evan <evanev7@gmail.com>
2026-03-03 10:38:52 +00:00
wysie 858dc808df fix: replace Master event_sender after EventRouter recreation (#1630) (#1637)
## Motivation
Fix for https://github.com/exo-explore/exo/issues/1630.
When loading a model that spans 2 nodes as the first action after
startup, Exo crashes with anyio.BrokenResourceError in
Master._command_processor. The receive end of the Master's event_sender
channel is already closed (open_receive_channels=0 ) when the Master
tries to send an InstanceCreated event.

<!-- Why is this change needed? What problem does it solve? -->
<!-- If it fixes an open issue, please link to the issue here -->

## Changes
In _elect_loop, when is_new_master is True and the EventRouter is
replaced, the Master's event_sender is now swapped in-place with a fresh
sender from the new EventRouter. This mirrors the existing pattern where
the Worker and DownloadCoordinator are already recreated with new
channels from the new router.
<!-- Describe what you changed in detail -->

## Why It Works
When is_new_master fires (which happens on every startup when a second
node joins), the old EventRouter is shut down and a new one is created.
Shutting down the old router cancels its _ingest coroutine, which closes
the receive end of every channel created by event_router.sender(). The
Worker and DownloadCoordinator are already recreated with senders from
the new router, but the Master was not — its event_sender still pointed
to the dead channel from the old router.
This patch replaces self.master.event_sender with a new sender from the
new EventRouter, so events flow through a live channel. We swap in-place
rather than recreating the Master to preserve its DiskEventLog, internal
state, and running tasks.
The reason loading a single-node model first appeared to work around the
issue is a timing race: if the model loads fast enough, the Master sends
its events before the election fires and kills the old channel. With a
2-node model, placement and RDMA setup take longer, so the election
completes first and the channel is already dead by the time the Master
tries to send.
<!-- Explain why your approach solves the problem -->

## Test Plan

### Manual Testing
<!-- Hardware: (e.g., MacBook Pro M1 Max 32GB, Mac Mini M2 16GB,
connected via Thunderbolt 4) -->
2 x Mac Studio M4 Max 128GB, RDMA
<!-- What you did: -->
Loaded a model that requires 2 nodes and it works properly now.
<!-- - -->

### Automated Testing
<!-- Describe changes to automated tests, or how existing tests cover
this change -->
<!-- - -->

Co-authored-by: Wysie <wysie@users.noreply.github.com>
2026-03-02 10:05:47 +00:00
ciaranbor 635118ef24 Support trace deletion in dashboard (#1628)
## Motivation

Enable using the dashboard to delete traces

## Changes

- Backend (src/exo/master/api.py): Added POST /v1/traces/delete endpoint
that deletes trace files by task ID, with path traversal protection
- API types (src/exo/shared/types/api.py): Added DeleteTracesRequest and
DeleteTracesResponse models
  - Dashboard store (app.svelte.ts): Added deleteTraces() method
- Traces page (+page.svelte): Added multi-select UI (click to select,
select all/deselect all) with a bulk delete button and confirmation
dialog

## Why It Works

- Uses existing _get_trace_path helper (now hardened against path
traversal) to resolve and delete trace files
- Dashboard selection state is reactive via Svelte 5 runes; deleted
traces refresh the list automatically

## Test Plan

### Manual Testing

- Select individual traces, select all, delete with confirmation dialog
  - Verify traces are removed from disk and list refreshes
2026-02-27 17:29:57 +00:00
Jake Hillion dc0bb5e13b fmt: add taplo TOML formatter to treefmt configuration 2026-02-27 17:19:48 +00:00
rltakashige 152a27ea5d Fix pipeline mismatched send after 1587 (#1629)
## Motivation

Tests caught a bug. It was a real bug.
2026-02-26 16:48:34 +00:00
rltakashige db36bd5ac6 Add custom prefill for pipeline (#1587)
## Motivation

Since we need to do distributed communications between prefill step
sizes, the out-of-the-box stream_generate that we currently use prevents
pipeline parallel models from doing overlapped computation. While this
was technically a regression, this communication is necessary for
cancellation, and we will need various distributed communications in the
future (e.g. for coordinating batching).

500 lines are for one testing file, so the diffs aren't as bad as they
look!

## Changes

Added a special prefill function for pipeline parallel models
Edited the model to handle 
Added a test to verify this new prefill and the original prefill produce
identical results
Improved type stubs to remove some type: ignores 

## Why It Works
<img width="768" height="1246" alt="image"
src="https://github.com/user-attachments/assets/8986ff17-ac23-4a02-9bd7-e6253a0ca799"
/>

## Test Plan

### Manual Testing
Needs more testing, but seems good so far.

### Automated Testing
Passes CI, considerable speedup seen in benchmarks (up to 1.98x) on
prefill speed.

Before:
<img width="3280" height="1238" alt="image"
src="https://github.com/user-attachments/assets/9abc1cbc-ecdb-4e48-a675-2c4cb04a32a0"
/>


After:
<img width="3344" height="1236" alt="image"
src="https://github.com/user-attachments/assets/e03c7987-41b4-4950-9ac3-2840e774ce30"
/>
2026-02-26 16:00:38 +00:00
Evan Quiney 639243aa09 event router (#1572)
replace the nack & resend logic in the worker/download coordinator with
a dedicated subsystem in front of the topic router. this centralizes
that logic (and the concept of system ids) to reduce replication in the
codebase. each system reading or writing events now gets a clean stream
of events in and can trust written events will be retried until
acknowledged.
2026-02-26 14:17:02 +00:00
Evan Quiney db73c4fd5d move messaging into rust (#1549)
the main body of the rust refactor. fixes the tokio panic on shutdown.
simplifies the networking module significantly. doesn't touch lp2p
behaviour
2026-02-26 13:58:22 +00:00
ciaranbor eaed92952c Use tmpdir for coordination file (#1624)
## Motivation

Coordination files for MLX distributed init were written to the current
working directory (./hosts_*.json)

## Changes

- Move coordination file creation to a tempfile.TemporaryDirectory(),
which auto-cleans on context manager exit
2026-02-26 10:59:36 +00:00
Evan Quiney ba611f9cd0 Revert "report macmon failures more aggressively (#1618)" (#1625)
this pr broke macmon - revert it
2026-02-25 19:15:55 +00:00
Evan Quiney eab3e0b456 report macmon failures more aggressively (#1618)
macmon appears to be going silent. time it out and restart it after
3*interval (Default 3s) and report warning.

Co-authored-by: Alex Cheema <41707476+AlexCheema@users.noreply.github.com>
2026-02-25 17:49:10 +00:00
Evan Quiney c4e874e97d skip nan logprobs on tokens (#1622)
sometimes we generate NaN logprobs for tokens, this causes pydantic
validation errors on the receiving end. in this case we should just not
send the logprob items
2026-02-25 17:44:15 +00:00
rltakashige e23c3a3026 Address Mac Mini pipeline GPU timeouts (#1620)
## Motivation
Users were reporting GPU timeout errors on Mac Minis, which we never saw
on testing with Mac Studios. It also seems to only happen with large
models.

## Changes
Eval specific distributed operations.

## Why It Works

As I wrote in a Slack message:
Basically, prefill is too slow for pipeline communications. If there are
both communications and GPU operations as part of an mlx graph, the
communications become subject to the GPU's 5 second command buffer
timeout.

For normal generation, I added evals to the communications (only during
prefill, as it slows down decode) to do this, fixing GPU timeouts.

But we don't do this during warmup, as the prompt is absolutely tiny.
This is still too slow on an M4 Pro on some models that it causes a GPU
timeout during warmup...


----------------------
This was one of the issues. However, there is another issue:

mx.all_gather sometimes reads stale data with FAST_SYNCH enabled. I'm
still investigating the root cause, but the code as it is now works on
Mac Minis.



## Test Plan

### Manual Testing
<img width="2762" height="1808" alt="image"
src="https://github.com/user-attachments/assets/27c88542-606c-4551-8f7c-bd2c0471f54e"
/>

<img width="2820" height="1898" alt="image"
src="https://github.com/user-attachments/assets/0ba3478c-ee39-438d-902c-92893db23d05"
/>


### Automated Testing
Needs a bunch on mac minis
2026-02-25 17:37:32 +00:00
Alex Cheema 190e63e56d fix: log exceptions causing silent node shutdown (#1621)
## Motivation

Nodes silently shut down mid-inference with no exception logged. The
logs show a clean-looking shutdown cascade (unsubscribe all topics →
stop worker → runner communication closed) but no error explaining
*why*. This makes debugging cluster issues extremely difficult.

## Changes

**`src/exo/routing/router.py`** — Added `try/except Exception` around
`_networking_recv()` and `_networking_recv_connection_messages()` loops.
Logs the root cause at ERROR level via
`logger.opt(exception=...).error(...)` before re-raising. Uses
`Exception` (not `BaseException`) so clean SIGTERM cancellation is
unaffected.

**`src/exo/main.py`** — Wrapped `anyio.run(node.run)` in
`try/except/finally`:
- `except BaseException`: logs the fatal exception at CRITICAL level
through loguru
- `finally`: ensures `logger.info("EXO Shutdown complete")` and
`logger_cleanup()` always run, guaranteeing the async log queue is
flushed before process exit

## Why It Works

The Router's recv loops (`_networking_recv`,
`_networking_recv_connection_messages`) are infinite `while True` loops
calling Rust bindings with **no exception handling**. When the Rust
networking layer raises `ConnectionError` (e.g. channel closed), the
exception cascades silently through anyio task groups: Router → Node →
all components shut down. The exception was never logged because (1) no
try-except anywhere in the cascade, and (2) `logger_cleanup()` was
skipped when `anyio.run()` raised, so loguru's async queue was never
flushed.

## Test Plan

### Manual Testing
- Run `uv run exo`, send SIGTERM → should see clean shutdown with "EXO
Shutdown complete", no error/critical logs
- Next time the silent shutdown reproduces, logs should now show the
actual exception with full traceback at ERROR level from the recv loop,
plus CRITICAL level from main()

### Automated Testing
- All 222 existing tests pass (1 pre-existing failure in
`test_python.py::test_sleep_on_multiple_items` unrelated to this change)
- `uv run basedpyright` — 0 errors
- `uv run ruff check` — all checks passed
- `nix fmt` — applied

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 08:24:18 -08:00
Alex Cheema 7660893538 fix: replace flaky internet checks with explicit offline mode (#1615)
## Motivation

The socket-based internet connectivity probes (connecting to
1.1.1.1/8.8.8.8/1.0.0.1 every 10 seconds) are flaky — they produce false
negatives on some networks/ISPs, causing downloads to silently fail or
get stuck. Instead of dynamically detecting connectivity, this replaces
it with an explicit offline mode that the user opts into.

## Changes

- **Removed internet check infrastructure**: Deleted
`_test_internet_connection()` (socket probes) and
`_check_internet_connection()` (10s polling loop) from
`DownloadCoordinator`
- **Renamed `internet_connection` → `offline`** on `ShardDownloader`
base class and all subclasses (`SingletonShardDownloader`,
`CachedShardDownloader`, `ResumableShardDownloader`)
- **Removed `on_connection_lost` callbacks** from download calls — no
longer needed without dynamic connectivity detection
- **Added `EXO_OFFLINE` env var support** in `constants.py` and `Args`
default in `main.py`
- **Added Offline Mode toggle** to macOS app Settings → General tab,
which sets `EXO_OFFLINE=true` in the process environment and restarts

## Why It Works

The download behavior (`skip_internet` conditionals in
`download_utils.py`) is unchanged — we just changed what drives it.
Instead of a flaky socket probe setting
`internet_connection=True/False`, the user explicitly sets offline mode
via:
1. `--offline` CLI flag
2. `EXO_OFFLINE=true` environment variable
3. macOS app Settings → General → Offline Mode toggle

This is more reliable and predictable. Users on flaky networks no longer
get intermittent download failures.

## Test Plan

### Manual Testing
- `uv run exo --offline` starts without internet checks, rejects
downloads for unavailable models
- `EXO_OFFLINE=true uv run exo` behaves identically
- macOS app Settings toggle persists across restarts and passes env var
to the exo process

### Automated Testing
- All existing tests pass (222 passed), including 8 offline-mode
specific tests
- `uv run basedpyright` — 0 errors
- `uv run ruff check` — all checks passed
- `nix fmt` — 0 files changed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 13:36:19 -08:00
Evan Quiney 9a2d2a4a7c bump (#1608)
should be release for 1.0.68 - let's synch our py version with our app
version - 0.3.68.
next minor release should be 1.4.0 and 0.4.0 respectively.

Co-authored-by: rltakashige <rl.takashige@gmail.com>
2026-02-24 19:14:15 +00:00
Alex Cheema bea64fe85e fix: prevent stale loading state and conversation loss when switching chats (#1613)
## Motivation

When navigating back to a previous conversation that used a different
model than the one currently loading, the UI incorrectly displayed the
other model's loading/download progress bar instead of the conversation
messages. Additionally, attempting to continue that old conversation by
sending a message would create an entirely new chat rather than
continuing the existing one.

## Changes

All changes in `dashboard/src/routes/+page.svelte`:

1. **Added fallthrough reset in the `chatLaunchState` restore
`$effect`**: When switching to a conversation whose model has no active
instance (not running, not downloading, not loading), the effect now
resets `chatLaunchState` to `"idle"` instead of leaving stale state from
a different model.

2. **Added `skipCreate` parameter to `launchModelForChat()`**: When
continuing an existing conversation (has messages),
`createConversation()` is skipped so the old conversation is preserved
rather than replaced with a new empty one.

3. **Reordered view conditional**: Progress views
(downloading/loading/launching) take priority, then conversation
messages (when idle with messages or model ready), then model selector
(idle with no messages). This ensures:
   - Old conversations display normally when their model isn't running
- Download progress shows when relaunching a model for any conversation
   - Model selector only appears when there are no messages

## Why It Works

- The `$effect` fallthrough prevents `chatLaunchState` from retaining a
stale value (e.g., `"downloading"`) from Model A when the user switches
to a conversation using Model B that has no active state.
- The `skipCreate` flag ensures `launchModelForChat` can be reused for
both new conversations (from model picker) and continuing existing ones
(from chat input) without always creating a new conversation.
- The reordered view logic ensures each state maps to the correct UI:
active launch → progress, existing messages → chat view, nothing → model
selector.

## Test Plan

### Manual Testing
- Load an old conversation while a different model is downloading →
should see conversation messages, not the other model's loading bar
- Send a message from that old conversation → model should
launch/download with progress shown → conversation continues with the
response appended
- Select a new model from the picker → should still create a new
conversation as before
- New conversation with model download → progress bar shows correctly

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 19:08:31 +00:00
rltakashige 14526d281a update mlx 2 (#1611)
## Motivation

GPU locks because of prompt progress callbacks taking time. Current
solution: Don't fix it, make the symptom better

## Changes
Shortened timeout by 2x
Get event leak fixes from latest upstream
2026-02-24 18:30:48 +00:00
Evan Quiney 73e50df827 fix glm5 tool calling (#1612)
glm5 is a deepseekv32 model, so was parsing dsml style tool calls
instead of glm style tool calls. fix it!!
2026-02-24 18:21:18 +00:00
Alex Cheema 2b417f28ff fix: sync model selectors between sidebar and chat input (#1610)
## Motivation

The "Load Model" dropdown in the right sidebar and the model selector
above the chat input were operating on independent state
(`selectedPreviewModelId` vs `selectedChatModel`). This caused several
UX issues:
- Selecting a model in one selector didn't update the other
- After going home from a chat, the chat selector showed "SELECT MODEL"
while the sidebar still showed the model
- On page refresh, only the sidebar restored the saved model
- Sending a chat message with a running model briefly showed the
recommended models view instead of starting the chat

## Changes

- **`handleModelPickerSelect`**: Also sets `selectedChatModel` when a
model is picked from the sidebar dropdown
- **`handleChatPickerSelect`**: Also sets `selectedPreviewModelId` when
a model is picked from the chat selector
- **`handleGoHome`**: Restores `selectedChatModel` from the sidebar's
`selectedModelId` instead of clearing it
- **`applyLaunchDefaults`**: Syncs `selectedChatModel` when restoring
saved defaults on page load
- **`handleChatSend`**: Sets `chatLaunchState = "ready"` before calling
`sendMessage` when the model is already running, ensuring the chat view
renders correctly on view transition

## Why It Works

The two selectors were backed by different store properties that were
never kept in sync. By updating both properties in every selection path
(sidebar pick, chat pick, go-home, page restore), they always reflect
the same model. The `chatLaunchState = "ready"` fix mirrors what
`launchModelForChat` already does (line 2649) and prevents the chat
state from being "idle" when transitioning from the welcome view to the
chat view.

## Test Plan

### Manual Testing
<!-- Hardware: MacBook Pro M4 Max 48GB -->
- Select a model from sidebar "Load Model" dropdown → chat input
selector updates to match
- Select a model from chat input selector → sidebar dropdown updates to
match
- Launch a model, chat with it, click "Go Home" → both selectors show
the same model
- Refresh the page → both selectors show the previously selected model
- With a running model, type and send a message from the welcome view →
chat starts directly without flashing the recommended models view

### Automated Testing
No new automated tests — this is a UI state synchronization fix in the
Svelte dashboard with no backend changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 17:31:20 +00:00
Alex Cheema b65982ddd7 fix: improve text contrast on HOME and DOWNLOADS nav links (#1609)
## Motivation

Follow-up to #1601 (downloads page contrast fix). The HOME and DOWNLOADS
navigation links in the top-right header use `text-exo-light-gray`
(`oklch(0.6 0 0)`) which is too dim against the dark header background.

## Changes

Changed both nav links in `HeaderNav.svelte` from `text-exo-light-gray`
to `text-white/70` for better visibility. Hover state
(`text-exo-yellow`) is unchanged.

## Why It Works

`text-white/70` provides noticeably better contrast against
`bg-exo-dark-gray` while still looking subdued relative to the yellow
accent color on hover. This is consistent with the approach used in
#1601.

## Test Plan

### Manual Testing
- Verified both links are clearly readable on the home page and
downloads page
- Hover state still transitions to yellow as expected

### Automated Testing
- Dashboard builds successfully

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 13:48:40 +00:00
rltakashige 2fe689315b download .model files in exo bench (#1607)
## Motivation

failed again for kimi on a machine that had never downloaded it.

## Test Plan

### Manual Testing
it worked this time
2026-02-24 11:13:04 +00:00
Alex Cheema 644c5573ce fix: improve text contrast on downloads page (#1601)
## Summary
- Bumps opacity on dark-grey text/icons on the downloads page that were
nearly invisible against the dark background
- Informational text (GB sizes, speeds, disk free, model IDs) → full
`text-exo-light-gray`
- Interactive icons (delete, resume, retry) → `/70` at rest
- Hover-only elements (download button) → `/60`

## Test plan
- [ ] Open http://localhost:52415/downloads with models downloaded
- [ ] Verify GB downloaded amounts are clearly visible
- [ ] Verify delete trash icons are visible (not just on hover)
- [ ] Verify download speed text is readable
- [ ] Verify "paused" labels and resume buttons are visible
- [ ] Verify "X GB free" disk labels in column headers are readable

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 10:42:49 +00:00
rltakashige 12c3015f52 fix qwen moe tensor sharding (#1604)
## Motivation

<!-- Why is this change needed? What problem does it solve? -->
<!-- If it fixes an open issue, please link to the issue here -->

## Changes

<!-- Describe what you changed in detail -->

## Why It Works

<!-- Explain why your approach solves the problem -->

## Test Plan

### Manual Testing
<!-- Hardware: (e.g., MacBook Pro M1 Max 32GB, Mac Mini M2 16GB,
connected via Thunderbolt 4) -->
<!-- What you did: -->
<!-- - -->

### Automated Testing
<!-- Describe changes to automated tests, or how existing tests cover
this change -->
<!-- - -->
2026-02-23 21:23:27 +00:00
rltakashige 365dd68d9a Final fixes for release (#1603)
## Motivation

<!-- Why is this change needed? What problem does it solve? -->
<!-- If it fixes an open issue, please link to the issue here -->

## Changes

<!-- Describe what you changed in detail -->

## Why It Works

<!-- Explain why your approach solves the problem -->

## Test Plan

### Manual Testing
<!-- Hardware: (e.g., MacBook Pro M1 Max 32GB, Mac Mini M2 16GB,
connected via Thunderbolt 4) -->
<!-- What you did: -->
<!-- - -->

### Automated Testing
<!-- Describe changes to automated tests, or how existing tests cover
this change -->
<!-- - -->
2026-02-23 21:10:15 +00:00
Alex Cheema d3d129581e test: verify instance deletion cancels ongoing tasks (#1508)
## Summary
- The cancellation logic for issue #1215 already exists in
`get_transition_events()` (`src/exo/master/placement.py:208-227`) — when
an instance is deleted, `TaskStatusUpdated(Cancelled)` events are
emitted for all Pending/Running tasks on that instance
- Combined with PR #1276's token-boundary cancellation in runners, the
full pipeline works end-to-end
- However, the existing test
`test_get_transition_events_delete_instance` passed `{}` for tasks, so
this path was never exercised
- This PR adds 4 tests covering the cancellation behavior:
  - Running tasks are cancelled on instance deletion
  - Pending tasks are cancelled on instance deletion
  - Completed/Failed/TimedOut/Cancelled tasks are left alone
  - Only tasks matching the deleted instance are cancelled

Closes #1215

## Test plan
- [x] `uv run pytest src/exo/master/tests/test_placement.py -v` — all 15
tests pass
- [x] `uv run basedpyright` — 0 errors
- [x] `uv run ruff check` — all checks passed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:12:23 +00:00
Alex Cheema c90a0cec78 fix: suppress closure errors in runnersupervisor and force spawn start method (#1547)
some errors could be thrown during shutdown - we can dismiss these safely

co-authored by me :)
2026-02-23 18:30:41 +00:00
Alex Cheema e8c1337168 fix: add download/resume buttons to pending downloads (#1581)
## Summary
- Adds a **resume button** (download icon) to paused pending downloads
(those with partial progress)
- Adds a **download button** to not-started pending downloads
- Both buttons call the existing `startDownload()` function which
handles both new downloads and resuming partial ones
- Previously, paused downloads only showed a "paused" label with no
action, and not-started downloads showed "..." with no way to trigger
them

## Test plan
- [ ] Build dashboard (`cd dashboard && npm run build`)
- [ ] Start exo, navigate to Downloads tab
- [ ] Verify paused downloads show a clickable resume (download arrow)
icon below the progress bar
- [ ] Verify not-started pending downloads show a clickable download
icon
- [ ] Click both button types and confirm downloads start/resume

> Note: Screenshot could not be captured because the dashboard requires
the exo backend API to render, and exo has a pre-existing
`Keypair.generate()` startup bug on main.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:20:35 +00:00
Alex Cheema 7024ddcf3e fix: detect completed downloads by checking final file exists (#1582)
## Summary

Split from #1547 per review feedback.

When scanning existing download status, `get_downloaded_size()` returns
bytes from either the final file or its `.partial` counterpart — so a
`.partial` file with all bytes downloaded (e.g. process killed before
hash verification and rename) could be falsely reported as complete.

The previous approach (commit 3b54e7df) added a byte-comparison fallback
in the coordinator (`downloaded >= total > 0`), but this suffered from
the same `.partial` conflation issue.

**Fix:** Check whether the final (non-`.partial`) file actually exists
on disk before marking status as `"complete"` in `download_utils.py`.
This is the only reliable signal that hash verification passed and the
rename from `.partial` succeeded. The coordinator-level byte comparison
is removed since the source now reports correctly.

### Changes
- `download_utils.py`: Add `final_file_exists` check — only report
`"complete"` when the renamed, hash-verified file exists with matching
size
- `coordinator.py`: Revert to simple `progress.status == "complete"`
check, removing the byte-comparison fallback

**Note:** The corresponding byte-comparison workaround in #1547 should
also be removed.

## Test plan
- [x] basedpyright — 0 errors
- [x] ruff check — passes
- [x] pytest — 218 passed, 1 skipped (1 pre-existing Rust bindings
failure)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:12:07 +00:00
Alex Cheema dc89ba662a feat: add info button to model picker variant rows (#1589)
## Motivation

When a model has multiple quantization variants (e.g., 4-bit, 8-bit),
expanding the group shows each variant's quantization and size — but
there's no way to see which specific nodes have each variant downloaded.
The top-level model group has an (i) info button, but the individual
variant rows don't. This makes it hard to tell which nodes have which
quantization.

## Changes

- Added an (i) info button to each expanded variant row in
`ModelPickerGroup.svelte`
- When clicked, opens the existing info modal scoped to that single
variant (showing its quantization, size, and which nodes have it
downloaded)
- Changed the variant row element from `<button>` to `<div
role="button">` to allow nesting the info `<button>` (matching the
pattern used by the top-level model row)

## Why It Works

Reuses the existing `onShowInfo` callback and info modal by constructing
a synthetic single-variant `ModelGroup` from the clicked variant. No
changes needed to `ModelPickerModal.svelte` — the info modal already
handles single-variant groups correctly.

## Test Plan

### Manual Testing
<!-- Hardware: MacBook Pro -->
- Open dashboard, open model picker
- Expand a model with multiple variants — each variant row now has an
(i) icon
- Click a variant's (i) — the info modal shows that single variant's
quantization/size and which nodes have it downloaded

### Automated Testing
- Dashboard builds successfully with `npm run build`
- `nix fmt` passes

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:53:36 -08:00
Alex Cheema 5cd96b5060 feat: seamless chat UX with auto model selection and smart recommendations (#1590)
## Motivation

The current chat experience requires users to manually select a model
before they can start chatting. This creates unnecessary friction —
especially for new users who may not know which model to pick.
Additionally, models that are already running or downloading appear
greyed out in the picker if available memory is low, making them
impossible to select even though they're already loaded.

## Changes

### New: ChatModelSelector component
- Category-based model recommendations (Best for Coding, Writing,
Agentic, Biggest) shown on the idle chat screen
- Tiered model ranking system (`AUTO_TIERS`) that picks the best model
fitting available memory
- Fixed-position tooltips that escape overflow-hidden ancestors

### Auto model selection from chat
- Typing in an empty chat auto-picks the best model using tier rankings
- Prefers already-running models over launching smaller new ones (avoids
the "launched a big model but recommends a smaller one" bug)
- If a model is already downloading/loading, attaches to existing
progress instead of launching a duplicate

### Launch-from-chat state machine
- Full `idle → launching → downloading → loading → ready` flow with
inline progress display
- `pendingAutoMessage` queue: messages typed during model launch are
remembered and sent once model is ready
- Fixed `$effect` race condition where restore effect competed with
auto-advance effect, causing lost messages

### Model picker improvements
- Instance status badges: green dot (ready), blue pulsing (downloading),
yellow pulsing (loading) on both group and variant rows
- "Ready" availability filter in ModelFilterPopover
- **Models with active instances (ready, downloading, loading) are never
greyed out** regardless of memory — they're already loaded/in-progress
and must be selectable

### Home page model display
- Model picker button shows the best running model name instead of "—
SELECT MODEL —" when a model is already active
- Uses the same `bestRunningModelId` tier-based derived as the chat
auto-selection

### Chat sidebar & form
- ChatSidebar conversation management with rename, delete, "New Chat"
- `userForcedIdle` guard prevents reactive effects from overriding
explicit user actions (e.g., clicking "New Chat")
- ChatForm simplified: uses ModelPickerModal instead of inline dropdown,
`modelDisplayOverride` prop for showing auto-selected models

## Why It Works

The tier-based auto-selection (`getAutoTierIndex` + `pickAutoModel`)
ensures users always get the best model their cluster can run. By
checking running instances first and comparing tiers, we avoid the bug
where loading a large model reduces available memory and causes a
smaller model to be recommended. The `$effect` ordering fix (moving the
`chatLaunchState === "ready"` guard after the `hasRunningInstance`
check) ensures pending messages are always delivered. The instance
status override in ModelPickerGroup (`anyVariantHasInstance`) ensures
models with active instances bypass memory-based greying regardless of
available RAM.

## Test Plan

### Manual Testing
<!-- Hardware: (e.g., MacBook Pro M1 Max 32GB, Mac Mini M2 16GB,
connected via Thunderbolt 4) -->
<!-- What you did: -->
- Launch exo, navigate to Chat view
- Verify idle chat shows category recommendations (Coding, Writing,
Agentic, Biggest) based on available memory
- Type a message without selecting a model → verify auto-selection picks
the best tier model
- Launch a model, go to New Chat → verify the input bar shows the
running model name (not "SELECT MODEL")
- Launch a model, go to Home → verify model picker button shows the
running model
- While a model is downloading, send a message → verify download
progress appears and message is queued
- Open model picker with a model running → verify it is NOT greyed out
and is selectable
- Click "New Chat" while in a conversation → verify it resets to idle
with model selector

### Automated Testing
- Dashboard builds successfully (`cd dashboard && npm run build`)
- No TypeScript errors in the build output


🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:32:55 -08:00
rltakashige 05986f77aa add exo bench protobuf dependency (#1596)
kimi k2.5 requires protobuf
2026-02-23 16:45:22 +00:00
vskiwi dab7ed4821 fix: handle gossipsub MessageTooLarge error to prevent silent crash (#1583)
## Summary

Large prompts (70K+ tokens / ~500KB+ JSON) cause exo to silently crash.
The root cause is an unhandled `PublishError::MessageTooLarge` from
gossipsub when serialized `TextGeneration` commands exceed the 1MB
`max_transmit_size` limit.

The error propagates as a generic Python exception through the PyO3
bindings. Since `_networking_publish` in `router.py` only catches
`NoPeersSubscribedToTopicError` and `AllQueuesFullError`, the unhandled
exception crashes the networking async task, causing exo to shut down
silently — no error message, no API response.

## Changes

- **Rust (PyO3 bindings):** Add `MessageTooLargeError` exception class
and handle `PublishError::MessageTooLarge` explicitly in the gossipsub
publish path, matching the existing pattern for
`NoPeersSubscribedToTopicError` and `AllQueuesFullError`
- **Python (router):** Catch `MessageTooLargeError` in
`_networking_publish` and log a warning with the message size,
preventing the networking task from crashing

## Reproduction

On a multi-node cluster with a large model (e.g., GLM-5 754B tensor
parallel over JACCL RDMA):
1. Send a chat completion request with ~70K+ tokens
2. exo silently shuts down — no error logged, curl gets no response
3. With shorter prompts (< ~50K tokens): works fine

## Test plan

- Verified `cargo check` passes for `networking` and `exo_pyo3_bindings`
crates
- Verified `ruff check` passes for modified Python files
- Manual testing on 4× Mac Studio M3 Ultra cluster: 50K token requests
pass, 70K+ previously caused silent shutdown, now logs a warning and
drops the oversized message gracefully

Co-authored-by: vsm <vsm@nomail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: rltakashige <rl.takashige@gmail.com>
2026-02-23 16:21:21 +00:00
Evan Quiney 2261014715 runner process checks (#1592)
partial solve to some of the more mysterious failures

notably, runner now switches to failed after EXO RUNNER MUST OOM
2026-02-23 16:11:18 +00:00
Evan Quiney 61d2a2b6cf add lazy task group (#1569)
in a few places we instantiate task groups early, in others we have an
optional task group.

this standardizes these patterns into a lazy task group, which queues
tasks until it is entered, at which point it enters its inner task group
and starts all its queued tasks. it should be noted that no queued tasks
will be started until the group itself is started.
2026-02-23 16:05:59 +00:00
Evan Quiney 0ff99a2c40 fix isinstance for qwen3Moe (#1595)
we were checking if qwen3 had a transformers Qwen3DecoderLayer rather
than an mlx Qwen3MoeDecoderLayer causing an assertion error on loading
qwen models - this corrects it to the actual layer type
2026-02-23 15:39:13 +00:00
rltakashige fbb80e1cc9 Address ring slowdown by turning on FAST SYNCH (#1594)
## Motivation

Large models + large prompts + pipeline RING = 0.2tps generation speed
Large models + large prompts + pipeline JACCL = 15 tps generation speed

Why? Well, MLX_METAL_FAST_SYNCH is on in pipeline JACCL.

## Changes

Just turn on fast synch everywhere, especially as GPU locks are old news

Also, changed to use mx.device_info as mx.metal.device_info is going to
be deprecated.

## Why It Works

Some magic thing that happens in the mlx backend. I really tried to find
a regression but couldn't. I will probably try again at some point.

## Test Plan

### Manual Testing
Did a bunch, no longer 0.2tps

### Automated Testing
We'll do that today.
2026-02-23 15:14:58 +00:00
Jake Hillion 8d94eab6c6 bench: fix KeyError on DownloadCompleted total field
The bench harness accessed the serialized DownloadCompleted field as
"totalBytes", but the Python field is `total: Memory` which serializes
to "total" (not snake_case, so the camelCase alias generator leaves it
unchanged). This caused a KeyError when --danger-delete-downloads
needed to free disk space by deleting existing models.

Changed the key from "totalBytes" to "total" to match the actual
serialized JSON structure.

Test plan:
- CI
- Reproduced KeyError on unfixed code by filling disk on a test node
  to 14GB free and running exo-bench with a 16GB model
  (Meta-Llama-3.1-8B-Instruct-bf16) with --danger-delete-downloads
- Verified fixed code successfully deletes smaller models to free
  space and completes the benchmark run
2026-02-23 14:17:41 +00:00
Alex Cheema f370452d7e Better onboarding UX (#1533)
## Summary
- **Complete onboarding wizard**: 7-step flow guiding new users from
Welcome → Your Devices (topology) → Add More Devices (animation) →
Choose Model → Download → Load → Chat
- **Native macOS integration**: NSPopover welcome callout anchored to
menu bar icon on first launch, polished DMG installer with
drag-to-Applications arrow
- **Dashboard UX polish**: auto-download on model select, toast
notifications, connection banner, skeleton loading, download progress in
header, recommended model tags, sidebar hidden in home state for cleaner
first impression
- **Settings & menu bar overhaul**: native Settings window with Advanced
tab, onboarding reset, chat sidebar toggle

## Test plan
- [ ] Fresh install: verify onboarding wizard appears and flows Welcome
→ Topology → Animation → Model → Download → Load → Chat
- [ ] Verify topology shows real device data in onboarding step 2
- [ ] Verify selecting a model in the main dashboard picker
auto-triggers download
- [ ] Verify chat sidebar is hidden on home view, appears when chat is
active
- [ ] Verify DMG installer has white background with curved arrow
- [ ] Verify NSPopover appears anchored to menu bar icon on first launch

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ryuichi Leo Takashige <leo@exolabs.net>
2026-02-23 11:27:28 +00:00
Alex Cheema a4c2aa2b87 fix: raise error when MlxJaccl requested without RDMA cycles (#1585)
## Summary
- When MlxJaccl (RDMA) placement is requested but no RDMA-connected
cycles exist, raise a clear ValueError instead of silently falling back
to non-RDMA cycles
- Split from #1519 for independent review

## Test plan
- [x] basedpyright — 0 errors
- [x] ruff check — passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:09:01 +00:00
Alex Cheema 7312c535b4 feat: add user context prompt and GitHub issue option to macOS bug report (#1544)
## Summary

- When clicking "Send Bug Report" in the macOS app, users are now
prompted with "What's the issue? (optional)" before the diagnostic
upload begins
- The user's description is included in the uploaded report JSON
(`user_description` field)
- After successful upload, a "Create GitHub Issue" button opens the
browser to `github.com/exo-explore/exo/issues/new` pre-filled with the
user's description, macOS version, and EXO version

## Changes

- **`ContentView.swift`**: Replaced simple button with multi-phase
inline UI (idle → prompting → sending → success/failure). Added
`openGitHubIssue()` helper using `URLComponents` for pre-filled GitHub
issue URLs.
- **`BugReportService.swift`**: Added `userDescription` parameter to
`sendReport()` and `makeReportJson()`, included in the JSON payload when
non-empty.
- Removed the previous `BugReportModal.svelte` approach (the feature
belongs in the macOS app, not the dashboard).

## Test plan

- [ ] Build the Xcode project and run the macOS app
- [ ] Click "Send Bug Report" → verify text prompt appears
- [ ] Type a description, click Send → verify upload succeeds and
"Create GitHub Issue" button appears
- [ ] Click "Create GitHub Issue" → verify browser opens with pre-filled
template
- [ ] Test Cancel returns to idle, test empty description still works
- [ ] Test failure case (e.g. with exo stopped) shows error and dismiss

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 15:37:43 +00:00
Alex Cheema 18717023ad chore: remove deprecated MlxIbv dashboard references (#1584)
## Summary
- Remove legacy MlxIbvInstance references from ChatSidebar and ModelCard
components
- MlxIbv was replaced by MlxJaccl; these are leftover type checks
- Split from #1519 for independent review

## Test plan
- [x] Visual inspection of dashboard components

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 06:56:12 -08:00
Alex Cheema 1780e4ade4 fix: change RDMA AVAILABLE to RDMA NOT ENABLED warning (#1580)
## Summary
- Changed blue info badge "RDMA AVAILABLE" to yellow warning badge "RDMA
NOT ENABLED" — more accurately describes the state
- Added hover tooltip with enable instructions to all views (was missing
in 2 of 4 instances)
- Warning icon instead of info icon, consistent with other cluster
warnings (TB cycle, macOS mismatch)

## Screenshots

**Badge (yellow warning):**
![RDMA warning
badge](https://raw.githubusercontent.com/exo-explore/exo/3f7bdb482c5011d60f140aa84ab21023032e4a57/rdma-warning.png)

**Hover tooltip with instructions:**
![RDMA warning
hover](https://raw.githubusercontent.com/exo-explore/exo/3f7bdb482c5011d60f140aa84ab21023032e4a57/rdma-warning-hover.png)

## Test plan
- [x] Dashboard builds successfully
- [ ] Verify badge appears when 2+ TB5 nodes have RDMA disabled
- [ ] Verify hover tooltip shows in normal layout
- [ ] Verify hover tooltip shows in topology-only mode
- [ ] Verify dismiss button works
- [ ] Verify compact badge in status bar shows yellow warning

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: rltakashige <rl.takashige@gmail.com>
2026-02-20 21:40:07 +00:00
Jake Hillion ab9273e723 downloads: add read_only flag to DownloadCompleted for EXO_MODELS_PATH
Models in EXO_MODELS_PATH are pre-downloaded into read-only directories
and must not be deleted. The DownloadCoordinator had no awareness of
these paths, so they never appeared as completed downloads in cluster
state, and the bench harness could attempt to delete them when freeing
disk space.

Added a `read_only: bool` field to `DownloadCompleted` (default False).
The DownloadCoordinator now checks `resolve_model_in_path` in
`_start_download`, proactively scans EXO_MODELS_PATH in
`_emit_existing_download_progress` to emit DownloadCompleted events for
all pre-downloaded models (overriding DownloadPending from the regular
scan), and refuses deletion of read-only models. The bench harness
filters out read-only models from deletion candidates.

Test plan:
- Ran with EXO_MODELS_PATH. Available models now show as downloaded in
  the UI. There isn't good UI for the fact they can't be deleted, but it
  should work with exo_bench.
2026-02-20 20:27:45 +00:00
Jake Hillion 71e48c0f62 model-cards: add missing metadata for Qwen3 Coder Next variants (#1576)
The Qwen3-Coder-Next model card TOML files were missing family,
quantization, base_model, and capabilities fields. This caused them not
to appear under the Qwen family filter in the dashboard's model picker.

Added the missing metadata to all five variants (4bit, 5bit, 6bit, 8bit,
bf16), matching the format used by the existing Qwen3-Coder-480B model
cards.

Test plan:
- Eyeballs
2026-02-20 18:25:49 +00:00
Jake Hillion 42da58c297 worker: add EXO_MODELS_PATH for pre-downloaded model directories
Users with pre-existing model files (e.g. on shared NFS mounts or from
prior downloads) had no way to point exo at those directories without
going through the download coordinator. EXO_MODELS_DIR only moves the
download target directory, it doesn't support read-only search paths.

Added EXO_MODELS_PATH environment variable as a colon-separated list of
directories to search for models. When the worker's plan loop encounters
a DownloadModel task, it checks these directories first and emits a
synthetic DownloadCompleted event if found, bypassing the download
coordinator entirely. The runner's build_model_path also checks these
directories first so the correct path is used during model loading.

This keeps the existing event sourcing state machine unchanged — the
DownloadCompleted event propagates naturally through the system, so
_load_model and all downstream logic work without modification.

Test plan:
- `s1@s1s-Mac-Studio ~ % EXO_LIBP2P_NAMESPACE=jake EXO_MODELS_PATH="/Volumes/Definitely Leo's SSD" nix --extra-experimental-features 'nix-command flakes' run github:exo-explore/exo/f2babbc2f742357d97dc177619fec062ef545be4`
- Started mlx-community/Qwen3-Coder-Next-4bit - it's present on the disk
  and it worked.
- Renamed one safetensor of mlx-community/Qwen3-Coder-Next-4bit on the
  disk. It then started the download locally, as expected.
2026-02-20 18:17:56 +00:00
Mustafa Alp Yılmaz 6b5a705959 fix: immediate cancel check after prefill completes (#1575)
## Problem

When a request is cancelled during prefill, the cancellation is not
detected until `check_for_cancel_every` additional tokens have been
generated. This is because `tokens_since_last_cancel_check` is
initialized to `0`, meaning the first cancel check only happens after
generating `check_for_cancel_every` tokens post-prefill.

For long prefills (which are the most likely to be cancelled), this adds
unnecessary latency before the cancellation is actually honoured.

## Fix

Initialize `tokens_since_last_cancel_check` to `check_for_cancel_every`
instead of `0`, so the very first token generated after prefill triggers
an immediate cancel check.

```diff
- tokens_since_last_cancel_check = 0
+ tokens_since_last_cancel_check = check_for_cancel_every
```

## Impact

- Cancellations issued during prefill are detected immediately when
generation begins
- No change in behaviour for non-cancelled requests (the counter resets
to `0` after each check as before)
- 1 line changed

Co-authored-by: rltakashige <rl.takashige@gmail.com>
2026-02-20 18:00:59 +00:00
Alex Cheema 6b54a27019 fix: add downloaded_bytes to DownloadPending event (#1564)
## Summary
- Add downloaded_bytes field to existing DownloadPending event for
accurate resume progress
- Minimal change per maintainer directive — no new download states
introduced

## Test plan
- [x] 42 tests passed, 1 skipped
- [x] Verified downloaded_bytes populates correctly for partial
downloads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: rltakashige <rl.takashige@gmail.com>
2026-02-20 17:54:18 +00:00
rltakashige e01f50a5cd Update mlx fork (#1565)
## Motivation

Some fixes upstream. This sort of commit will probably be quite common
until GPU locks are resolved.
2026-02-20 17:23:52 +00:00
Evan Quiney 1093080214 cancel active downloads on coordinator shutdown (#1567)
we were seeing some crashes as lost download tasks were trying to push
data toward a deleted coordinator. this cancels download tasks with the
coordinator's shutdown on master election
2026-02-20 17:17:43 +00:00
rltakashige 1a2b8b044a Refactor runner into separate runners (#1570)
## Motivation

We're going to be refactoring the llm inference code, so we should split
the runner up into parts while we can.

## Test Plan

### Manual Testing
Works on single node, at least.

### Automated Testing
Passes CI. Will be tested by our tests today.
2026-02-20 17:11:01 +00:00
Evan Quiney dc8d42b4dc add system ids (#1536)
addresses some election edge cases where a new worker with an old master
would get stuck on the old workers buffer index - we now use new system
ids each time we instantiate a node, and each event-producing system has
a unique system id for its lifespan (until the master moves).
2026-02-20 15:41:59 +00:00
Jake Hillion d484b062e8 bench: add download timing to bench output (#1566)
The bench script downloads models during the planning phase but doesn't
record how long the download took, making it difficult to track download
performance for a given model over time.

Modified `run_planning_phase` to return download metadata: whether a
fresh download occurred, the wall-clock duration, and the model size in
bytes. These fields are included in every JSON output row alongside the
existing per-run metrics, and a summary line is logged to the console.

This allows filtering bench results by `download_occurred` and grouping
by `model_id` to compute average download times across runs.

Test plan:

```
# existing model
jake@maverick:/data/users/jake/repos/exo/ > nix run .#exo-bench -- --host s1 --model mlx-community/gpt-oss-120b-MXFP4-Q8 --pp 128 --tg 128
...
2026-02-20 15:23:49.081 | INFO     | __main__:main:340 - Planning phase: checking downloads...
2026-02-20 15:23:49.152 | INFO     | harness:run_planning_phase:402 - Started download on 12D3KooWKx41iikn188ozrxSdoG26g88jFCfie9wEA1eQR8csbPm
2026-02-20 15:23:49.184 | INFO     | __main__:main:352 - Download: model already cached
...
Wrote results JSON: bench/results.json
jake@maverick:/data/users/jake/repos/exo/ > cat bench/results.json
[
  {
    "elapsed_s": 2.9446684420108795,
    "output_text_preview": "The user just typed a long series of \"a\". Possibly they are testing. There's no explicit question. Could be they want a response? Might be a test of handling long input. We can respond politely, ask i",
    "stats": {
      "prompt_tps": 117.7872141515621,
      "generation_tps": 85.49598231498028,
      "prompt_tokens": 129,
      "generation_tokens": 128,
      "peak_memory_usage": {
        "inBytes": 68215145744
      }
    },
    "model_short_id": "gpt-oss-120b-MXFP4-Q8",
    "model_id": "mlx-community/gpt-oss-120b-MXFP4-Q8",
    "placement_sharding": "Pipeline",
    "placement_instance_meta": "MlxRing",
    "placement_nodes": 1,
    "instance_id": "68babc2a-6e94-4c70-aa07-7ec681f7c856",
    "pp_tokens": 128,
    "tg": 128,
    "repeat_index": 0
  }
]%
# no change to output
```

```
# missing model
jake@maverick:/data/users/jake/repos/exo/ > nix run .#exo-bench -- --host s1 --model mlx-community/Meta-Llama-3.1-8B-Instruct-4bit --pp 128 --tg 128
...
2026-02-20 15:24:42.553 | INFO     | __main__:main:340 - Planning phase: checking downloads...
2026-02-20 15:24:42.625 | INFO     | harness:run_planning_phase:402 - Started download on 12D3KooWKx41iikn188ozrxSdoG26g88jFCfie9wEA1eQR8csbPm
2026-02-20 15:25:37.494 | INFO     | __main__:main:350 - Download: 54.9s (freshly downloaded)
...
Wrote results JSON: bench/results.json
jake@maverick:/data/users/jake/repos/exo/ > cat bench/results.json
[
  {
    "elapsed_s": 1.500349276990164,
    "output_text_preview": "It seems like you've entered a large number of 'a's. If you'd like to discuss something or ask a question, I'm here to help. If not, is there anything else I can assist you with? \n\nIf you're intereste",
    "stats": {
      "prompt_tps": 395.43264952543666,
      "generation_tps": 128.03520443181478,
      "prompt_tokens": 129,
      "generation_tokens": 128,
      "peak_memory_usage": {
        "inBytes": 5116952079
      }
    },
    "model_short_id": "Meta-Llama-3.1-8B-Instruct-4bit",
    "model_id": "mlx-community/Meta-Llama-3.1-8B-Instruct-4bit",
    "placement_sharding": "Pipeline",
    "placement_instance_meta": "MlxRing",
    "placement_nodes": 1,
    "instance_id": "ccd9bd71-d4cc-4b75-a37f-98090544626a",
    "pp_tokens": 128,
    "tg": 128,
    "repeat_index": 0,
    "download_duration_s": 54.88322358299047
  }
]%
# one new field
```
2026-02-20 15:33:08 +00:00
Alex Cheema e32b649d2f fix: enable psutil fallback for memory monitoring when macmon is missing (#1478)
## Summary
- On macOS, memory monitoring relied exclusively on `macmon` — the
psutil fallback was explicitly disabled (`memory_poll_rate = None`)
- When `macmon` is not installed (e.g., mac-mini-2 through mac-mini-4 in
our cluster), **no memory data was reported**, causing nodes to show 0GB
memory in the cluster state
- This blocked the scheduler from placing shards on those nodes since it
had no memory data to work with
- Fix: when `macmon` is not found on Darwin, fall back to psutil-based
memory polling (`memory_poll_rate = 1`)

## Root cause
`InfoGatherer` has two memory monitoring paths:
1. `macmon` (Darwin-only): provides memory + GPU/CPU/power stats
2. `psutil` (non-Darwin fallback): provides memory via
`MemoryUsage.from_psutil()`

Line 378 disabled psutil on Darwin: `memory_poll_rate = None if
IS_DARWIN else 1`
Line 389 only starts macmon if the binary exists: `if
shutil.which("macmon") is not None`

If macmon is missing on Darwin, **neither path runs** — zero memory
reported.

## Test plan
- [ ] Verify `uv run basedpyright` passes (0 errors confirmed)
- [ ] Verify `uv run ruff check` passes (confirmed)
- [ ] Verify `uv run pytest src/exo/utils/info_gatherer/` passes (2/2
confirmed)
- [ ] Deploy to cluster nodes without macmon and verify memory appears
in `/state`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: rltakashige <rl.takashige@gmail.com>
2026-02-20 13:12:22 +00:00
Alex Cheema bddad7e79c feat: show ETA on prefill progress bar (#1557)
## Summary
- Show estimated time remaining during prefill (prompt processing phase)
- Track prefill start time via performance.now() and extrapolate from
observed token throughput
- Display ~Xs remaining or ~Xm Ys remaining next to the percentage on
the progress bar
- Wait 200ms before showing ETA to ensure a stable sample window

## Changes
**PrefillProgressBar.svelte**: Add etaText derived computation that
calculates remaining time from (remainingTokens / tokensPerMs). Renders
in a new flex row below the progress bar alongside the percentage.

**app.svelte.ts**: Add startedAt: number field to PrefillProgress
interface. Set on first prefill_progress SSE event, preserved across
subsequent updates.

## Test plan
- [ ] Start inference with a long prompt (10k+ tokens) on a multi-node
cluster
- [ ] Verify the progress bar shows ~Xs remaining after ~200ms of
prefill
- [ ] Verify the ETA decreases as prefill progresses
- [ ] Verify short prefills (<200ms) dont flash a briefly-visible ETA
- [ ] Verify ETA disappears when prefill completes and token generation
begins

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: rltakashige <rl.takashige@gmail.com>
2026-02-20 12:37:56 +00:00
rltakashige addf73a144 Add support for Ollama API (#1560)
## Motivation

Ollama has a bunch of integrations, such as OpenWebUI, that are very
handy. Let's support it :)

## Test Plan

### Manual Testing
<img width="3426" height="1998" alt="image"
src="https://github.com/user-attachments/assets/44b07f1e-308e-4ff1-9a11-922d8279939f"
/>
2026-02-20 12:03:27 +00:00
Mustafa Alp Yılmaz a16ff2c047 fix: correct misleading docstring in seed_models (#1561)
## Summary
- Fixed stale docstring in `seed_models()` that referenced
`.cache/huggingface/hub` when the function actually moves models to
`EXO_MODELS_DIR` (resolved via `ensure_models_dir()`)
- The old docstring was misleading for AI coding agents analyzing the
codebase, causing incorrect conclusions about model storage paths

## Changes
`src/exo/download/download_utils.py`: Updated docstring from `"Move
model in resources folder of app to .cache/huggingface/hub"` to `"Move
models from resources folder to EXO_MODELS_DIR."`

Co-authored-by: rltakashige <rl.takashige@gmail.com>
2026-02-20 11:57:55 +00:00
rltakashige 3006c8ea4e Ensure coordinator is rank 0 (#1559)
## Motivation

Coordinator can be a random rank. Let's just fix this to rank 0 as
that's what we typically assume.

## Test Plan

### Manual Testing
Works as normal on 2 nodes.


Let's wait for a little more testing to merge this.

---------

Co-authored-by: Evan <evanev7@gmail.com>
2026-02-20 11:46:24 +00:00
rltakashige f662c129dd Prioritise tb for ring instances (#1556)
## Motivation

TB has better bandwidth and latency than ethernet. We should prioritise
TB5 where possible. This drastically improves distributed image
generation performance.

## Test Plan

### Manual Testing
Saw on the dashboard that TB (169.254) addresses were prioritised.

Tested that image models scale much better.

### Automated Testing
No regression on Kimi K2.5
2026-02-19 21:32:48 +00:00
Evan Quiney c45ff9ad43 memory tidy (#1558)
add some pythonic extensions to memory, did a bunch of cleanup.
2026-02-19 21:15:33 +00:00
rltakashige 7031901ae5 Prevent common fatal crashes (#1555)
## Motivation
Occasionally, memory does not get released when we shut down. There is
no reason to delay deleting the model.

Also handles can become None during shutdown, causing TypeErrors which
are not handled and bringing down exo.

Similarly, we were closing the event sender in the wrong place.

Also let's not verify the SSL certificate for http connections to local
peers, as this is failing sometimes and crashing.

## Test Plan

### Manual Testing
No more crashes as described.
2026-02-19 20:51:17 +00:00
rltakashige cf648a53b8 Add thinking in thinking blocks, and fix DeepSeek interleaved tool calls (#1548)
## Motivation

OpenCode shows <think> tags and not thinking blocks as we aren't
following the API specs properly.

Claude was also getting horrible prefix cache hits because it sends
headers.

## Changes

Handle thinking tokens properly by placing them in think tags for each
of the API endpoints.
Also support DeepSeekV3.2 tool calling properly as a minor feature.
Strips Claude headers at the API level.

## Test Plan

### Manual Testing
Tested OpenCode manually
Needs testing with Claude.

### Automated Testing
All CI and tests passing - added a new e2e test for DeepSeekV32 tool
parsing.
2026-02-19 18:44:49 +00:00
Alex Cheema 94b2ce6922 feat: Mac Studio en2 RDMA port warning v2 (#1551)
Rebuilt from scratch (replaces PR #1543). Detects when Mac Studio uses
RDMA over en2 (TB5 port next to Ethernet) which does not support RDMA.
Shows dismissible warning banner with hover tooltip showing affected
devices, SVG rear panel illustration, and fix instructions. 205 lines in
+page.svelte.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: rltakashige <rl.takashige@gmail.com>
2026-02-19 18:39:17 +00:00
rltakashige 423ed0f07f Strip Claude headers to improve prefix cache hit rates (#1552)
## Motivation
Our hits are really bad at the moment (0.2%). This PR makes it 98.5% on
average.

## Changes

Also adds an example for how to run Claude using Exo.

## Why It Works
Claude sends some billing and session headers that change with each
message.

## Test Plan

### Manual Testing
Works in manual testing.
2026-02-19 18:29:34 +00:00
Evan Quiney ed001f2409 remove prefillprogress event (#1550)
this should never have been a separate event, but i didnt quite
communicate that well when this was merged. convert PrefillProgress to a
chunk like the rest of the runner responses.

tested with Llama-3.3-70B, prefill progress events still show up in the
dashboard as usual
2026-02-19 18:23:28 +00:00
Evan Quiney 4c4c6ce99f simplify rust ident module
this is partly dead code, partly narrowing the rust-python boundary in
prep for future rewrites. no testing as this is all type safe
refactoring.
2026-02-19 17:19:31 +00:00
Jake Hillion 42e1e7322b bench: restore --danger-delete-downloads planning phase (#1542)
c2f2111b extracted shared utilities from exo_bench.py into harness.py
but accidentally dropped the run_planning_phase function and
--danger-delete-downloads CLI argument in the process.

Restored run_planning_phase in harness.py (where its dependencies now
live) and re-added the --danger-delete-downloads argument to
add_common_instance_args. Re-wired the planning phase call in
exo_bench.py's main() before the benchmark loop.
2026-02-19 15:42:02 +00:00
Alex Cheema aa3f106fb9 fix: import ResponsesStreamEvent and DRY up SSE formatting (#1499)
## Summary
- `ResponsesStreamEvent` was defined in `openai_responses.py` as a union
of all 11 streaming event types but never imported or used anywhere in
the codebase
- Import it in the responses adapter and add a `_format_sse(event:
ResponsesStreamEvent) -> str` helper
- Replace 13 hardcoded `f"event: {type}\ndata:
{event.model_dump_json()}\n\n"` strings with `_format_sse()` calls

## Test plan
- [x] `uv run basedpyright` — 0 errors
- [x] `uv run ruff check` — all checks passed
- [x] `nix fmt` — 0 files changed
- [x] `uv run pytest` — 188 passed, 1 skipped

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:40:24 +00:00
Mustafa Alp Yılmaz 2e29605194 fix: finalize cancel tasks (#1498)
# Cancel task finalization (main.py)

After forwarding the cancel to the runner supervisor, emit TaskStatusUpdated(Complete) for the cancel task itself. This ensures the cancel task is properly removed from state.tasks.
2026-02-19 13:27:34 +00:00
Evan Quiney cacb456cb2 remove nightly (#1538)
we have no good need for rust nightly (nor futures, for that matter)
2026-02-19 12:55:31 +00:00
rltakashige 51021f6fc6 Add cancellation button and the ability to cancel during prefill (#1540)
## Motivation
There's no way to easily use the cancellation features we added! Also,
prefill can take ages so let's allow cancelling out of that.

## Changes

Wiring up our existing functionality to easily cancel during generation
(and adding stuff to do so during prefill)

## Test Plan

### Manual Testing
Tested it works during both prefill and decode.

### Automated testing
Needs testing to see if this causes a GPU timeout error on large prefill
on large models in pipeline parallel. However, from manually testing GLM
5 pipeline ring on 2 nodes, and from reading the code, it does not seem
like this will be the case.
2026-02-19 11:40:59 +00:00
Alex Cheema 025ed9fd82 feat: add prefill progress bar for long prompts (#1181)
## Motivation

Users processing long prompts have no visibility into when token
generation will start. This feature adds a progress bar showing prefill
progress, giving users real-time feedback during prompt processing.

## Changes

### Backend
- Added `PrefillProgress` event type with `command_id`,
`processed_tokens`, `total_tokens`
- Added `PrefillProgressResponse` type (though now using direct callback
approach)
- Wired `prompt_progress_callback` through MLX's `stream_generate()`
- Progress events sent directly from callback for real-time updates (not
batched)
- API generates SSE named events: `event: prefill_progress\ndata: {...}`
- Added `PrefillProgressData` dataclass and `StreamEvent` union type in
API

### Dashboard
- Added `PrefillProgress` interface to store
- Updated SSE parsing to handle `event:` lines (named events)
- Created `PrefillProgressBar.svelte` with animated progress bar
- Shows "Processing prompt: X/Y tokens" with percentage
- Progress bar disappears when first token arrives

## Why It Works

MLX's `stream_generate()` accepts a `prompt_progress_callback(processed,
total)` that's called after each prefill chunk. By sending events
directly from this callback (rather than yielding from the generator),
progress updates are sent in real-time during prefill.

Using SSE named events (`event: prefill_progress`) maintains full
OpenAI/Claude API compatibility - standard clients ignore named events
they don't recognize, while the exo dashboard explicitly listens for
them.

## Test Plan

### Manual Testing
- Hardware: MacBook Pro M3 Max
- Set `prefill_step_size=256` for more frequent updates
- Tested with long prompts (pasted large documents)
- Verified progress bar updates incrementally during prefill
- Confirmed progress bar disappears when generation starts
- Tested with curl - standard `data:` events still work normally

Here is it working:


https://github.com/user-attachments/assets/5cc6f075-c5b2-4a44-bb4d-9efb246bc5fe


### Automated Testing
- Type checker passes (0 errors)
- All 192 tests pass
- Dashboard builds successfully

### API Compatibility
- Named SSE events are ignored by OpenAI SDK clients
- Regular token data uses standard `data: {...}` format
- `[DONE]` sentinel works as expected

---

**Note:** `prefill_step_size` is temporarily set to 256 for testing.
Should be changed back to 2048 before merging for production
performance.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Evan <evanev7@gmail.com>
Co-authored-by: Ryuichi Leo Takashige <leo@exolabs.net>
2026-02-19 03:18:25 +00:00
rltakashige 19bc09550d Add status=downloaded filter for model endpoint (#1539)
## Motivation

https://github.com/exo-explore/exo/issues/1346#issuecomment-3831427905


## Test Plan

### Manual Testing
**Without filter**
<img width="1708" height="1010" alt="Screenshot 2026-02-18 at 22 26 22"
src="https://github.com/user-attachments/assets/f4bf7142-717d-4042-ac28-d8a55a8e45e7"
/>

**With filter**
<img width="1723" height="1021" alt="Screenshot 2026-02-18 at 22 26 45"
src="https://github.com/user-attachments/assets/40a522d5-c6e6-4148-b21a-02caa1221ebe"
/>
2026-02-18 22:34:11 +00:00
Alex Cheema 7cadca4f27 Try multiple endpoints for internet connectivity check (#1516)
## Summary
- `_test_internet_connection()` previously only tried `1.1.1.1:443`,
which some ISPs/networks block, causing exo to incorrectly report no
internet and fail downloads on startup
- Now tries `1.1.1.1`, `8.8.8.8`, and `1.0.0.1` in sequence, succeeding
if any endpoint responds
- Returns early on first success for minimal latency in the common case

Fixes #1425

## Test plan
- [ ] Verify downloads work on networks that block `1.1.1.1`
- [ ] Verify existing behavior unchanged on networks where `1.1.1.1`
works
- [ ] Verify `internet_connection` is set to `False` only when all three
endpoints fail

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: rltakashige <rl.takashige@gmail.com>
2026-02-18 22:10:07 +00:00
rltakashige 24e99ce197 Cleanup mistakes (#1537)
Oops
2026-02-18 22:05:26 +00:00
Alex Cheema 315992549b fix: unblock MpReceiver.close() to prevent shutdown hang (#1511)
## Summary

- `MpReceiver.close()` did not unblock threads stuck on `queue.get()` in
`receive_async()`, causing abandoned threads (via
`abandon_on_cancel=True`) to keep the Python process alive indefinitely
after tests pass
- This caused the `aarch64-darwin` CI jobs in PR #1462 to hang for ~6
hours until the GitHub Actions timeout killed them
- Sends an `_MpEndOfStream` sentinel before closing the buffer,
mirroring what `MpSender.close()` already does

## Test plan

- [x] `uv run basedpyright` — 0 errors
- [x] `uv run ruff check` — clean
- [x] `nix fmt` — 0 changed
- [x] `uv run pytest` — 188 passed, 1 skipped in 12s (no hang)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: rltakashige <rl.takashige@gmail.com>
Co-authored-by: Ryuichi Leo Takashige <leo@exolabs.net>
2026-02-18 21:59:02 +00:00
Alex Cheema ce5a65d3b9 Add MiniMax M2.5 model cards (#1514)
## Summary
- Adds model cards for MiniMax M2.5 in three quantizations: 4bit (~129
GB), 6bit (~186 GB), 8bit (~243 GB)
- No code changes needed — `MiniMaxM2ForCausalLM` is already in the
tensor parallel whitelist and `MiniMaxShardingStrategy` is already
implemented in `auto_parallel.py`
- Credit to @vskiwi for confirming MiniMax M2.5 works out of the box
with existing code

Closes #1480

## Test plan
- [x] `basedpyright` passes with 0 errors
- [x] `ruff check` passes
- [x] `pytest` passes (260 passed, 1 skipped)
- [ ] Verify MiniMax M2.5 models appear in model selector on dashboard

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: rltakashige <rl.takashige@gmail.com>
2026-02-18 21:11:13 +00:00
rltakashige c2f2111b88 Fix tool calling (#1529)
## Motivation

GPT OSS tool calling issues.

## Changes

Fixes those and adds a bunch of evals for tool calling.
Fixes GLM5 prefix caching, where CacheList wasn't getting handled
properly.
Extracts a bunch of the setup functionality of exo bench to a harness
that can be reused elsewhere, such as in the tool calling eval.

## Test Plan
### Automated Testing
Let's run the evals for all models
2026-02-18 20:29:18 +00:00
Alex Cheema 6c322ebb72 feat: only show thinking toggle for models that support it (#1497)
## Summary
- Adds `thinking_toggle` capability to 26 model cards that support
toggling thinking mode on/off
- GPT-OSS models (20b, 120b) excluded — they always think and don't
support toggling
- Dashboard UI updated to check for `thinking_toggle` capability before
showing the toggle button

## Test plan
- [x] `uv run basedpyright` — 0 errors
- [x] `uv run ruff check` — all checks passed
- [x] `nix fmt` — 0 files changed
- [x] `uv run pytest` — 188 passed, 0 failed
- [x] Security review passed (no secrets, eval/exec, innerHTML, or dep
changes)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 17:05:00 +00:00
vskiwi 2ebe6216b4 feat: add explicit --offline mode for air-gapped clusters (#1525)
## Motivation

Closes #1510

There is currently no reliable way to run exo on an air-gapped or offline cluster where models are pre-staged on local disks. The two existing mechanisms — `--no-downloads` and `HF_HUB_OFFLINE=1` — each cover only a subset of the problem:

1. **`--no-downloads` blocks model loading**: When passed, `DownloadCoordinator` is not created. No `NodeDownloadProgress` events are ever emitted, so `_model_needs_download()` in `plan.py` perpetually returns `DownloadModel`, short-circuiting `_load_model()` and preventing the model from ever being loaded.

2. **`HF_HUB_OFFLINE=1` doesn't cover exo's aiohttp code**: exo's download pipeline primarily uses raw `aiohttp` for HTTP operations (file list fetching, file downloads, HEAD verification), not the `huggingface_hub` library. These calls will attempt connections and time out on air-gapped networks.

3. **`skip_internet` is not propagated to `download_file_with_retry()`**: Even when `internet_connection = False`, the `_download_file()` function still makes HTTP HEAD calls via `file_meta()` to verify local files and unconditionally attempts downloads for missing files.

## Changes

### `src/exo/main.py`
- Add `--offline` flag to `Args` with env var detection (`EXO_OFFLINE=1`, `HF_HUB_OFFLINE=1`)
- Pass `offline` to `DownloadCoordinator` at creation and re-creation (election loop)

### `src/exo/download/coordinator.py`
- Add `offline: bool = False` field
- In offline mode: set `internet_connection = False` immediately in `__post_init__`, skip `_test_internet_connection()` ping (avoids 3s timeout), skip `_check_internet_connection` periodic loop
- In `_start_download()`: if model is not fully available locally, emit `DownloadFailed` with clear message instead of starting a download task

### `src/exo/download/download_utils.py`
- Add `skip_internet: bool` parameter to `download_file_with_retry()` and `_download_file()`
- When `skip_internet=True` in `_download_file()`: return local file immediately without HTTP HEAD verification; raise `FileNotFoundError` for missing files
- Propagate `skip_internet` from `download_shard()` to `download_file_with_retry()`

### `src/exo/download/tests/test_offline_mode.py` (new)
- 8 tests covering `_download_file`, `download_file_with_retry`, and `fetch_file_list_with_cache` in offline mode

## Why It Works

Unlike `--no-downloads` which disables `DownloadCoordinator` entirely, `--offline` keeps the coordinator running in a restricted mode. The existing `_emit_existing_download_progress()` disk scanner still runs every 60 seconds, emitting `DownloadCompleted` events for pre-staged models. These events flow through the event-sourcing pipeline and populate `state.downloads`, which unblocks `_model_needs_download()` in `plan.py` — no changes to the planning logic required.

```
--offline flag
  → DownloadCoordinator (offline mode)
    → Skip 1.1.1.1 ping, internet_connection = False
    → _emit_existing_download_progress scans disk
      → Emits DownloadCompleted for pre-staged models
        → _model_needs_download sees DownloadCompleted
          → _load_model proceeds normally
```

## Test Plan

### Automated Testing
- `ruff check` — passes
- 8 new tests in `test_offline_mode.py` — all pass
- 11 existing download tests in `test_download_verification.py` — all pass (no regressions)

### Manual Testing
1. Pre-stage a model on disk (e.g., `~/.exo/models/mlx-community--Qwen3-0.6B-4bit/`)
2. Start exo with `--offline` (or `EXO_OFFLINE=1`)
3. Place an instance via API or dashboard
4. Verify: model loads into memory and inference works without any network calls

### Environment
- macOS (Apple Silicon), multi-node cluster with Thunderbolt interconnect
- Models pre-staged via rsync / NFS mount
2026-02-18 16:18:09 +00:00
ciaranbor f54c80b121 Ciaran/image edit api (#1500)
## Motivation

- Image editing previously ignored input image dimensions, always
defaulting to 1024x1024
- Size dropdown was hidden in edit mode, giving users no control over
output dimensions
- Portrait/landscape presets used non-standard aspect ratios (1024x1365
/ 1365x1024)

## Changes

- Added "auto" size option that uses input image dimensions for edits,
defaults to 1024x1024 for generation
- Introduced ImageSize Literal type and normalize_image_size() validator
(replaces raw str size fields)
  - Updated portrait/landscape presets to standard 1024x1536 / 1536x1024
  - Made size selector visible in edit mode (previously hidden)
  - Default size changed from "1024x1024" to "auto"

## Why It Works

- "auto" reads actual input image dimensions via PIL at generation time,
so edits preserve the original aspect ratio
- Pydantic field_validator on both ImageGenerationTaskParams and
ImageEditsTaskParams normalizes None → "auto", keeping the API
backward-compatible

## Test Plan

### Manual Testing

- Verify image edits output at the input image's native resolution when
size is "auto"
- Verify size dropdown appears and works in both generate and edit modes
2026-02-18 16:05:39 +00:00
rltakashige 48b8f86395 Add support for GLM 5 (#1526)
## Motivation

Add GLM 5 support in favor of #1513 

## Changes

<!-- Describe what you changed in detail -->

## Why It Works

<!-- Explain why your approach solves the problem -->

## Test Plan

### Manual Testing
<!-- Hardware: (e.g., MacBook Pro M1 Max 32GB, Mac Mini M2 16GB,
connected via Thunderbolt 4) -->
<!-- What you did: -->
<!-- - -->

### Automated Testing
<!-- Describe changes to automated tests, or how existing tests cover
this change -->
<!-- - -->
2026-02-18 14:04:06 +00:00
Evan 5cbd6377a2 prioritize official model cards over custom model cards
our old model card search path would override official model cards with
custom model cards - our packaged model cards should always be the
default here
2026-02-18 13:20:05 +00:00
Evan Quiney 8f01523ddb remove dead code (#1496) 2026-02-18 11:43:27 +00:00
Alex Cheema 3addeadea8 Update mlx-lm to 0.30.7 (#1520)
## Summary
- Bumps `mlx-lm` from 0.30.6 to 0.30.7 in `pyproject.toml` and `uv.lock`

## Test plan
- [x] `uv lock` resolves successfully
- [x] `basedpyright` — no new errors (63 pre-existing in unrelated
`test_tool_call_tracker.py`)
- [x] `ruff check` — all checks passed
- [x] `nix fmt` — no formatting changes
- [x] `pytest` — 188 passed, 1 skipped

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 11:14:23 +00:00
rltakashige f2be929211 Leo/address rdma gpu locks 2 (#1515)
Same as #1489 . Had to revert and redo thanks to Claude.

---------

Co-authored-by: Jake Hillion <jake@hillion.co.uk>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 14:00:52 -08:00
rltakashige 83af8c63fa Revert "Use custom fork that resolves GPU locks" (#1502)
Reverts exo-explore/exo#1489

Goddammit Claude...
2026-02-17 18:18:54 +00:00
Evan Quiney eccc6298d1 Revert "Add MetaInstance declarative layer (#1447)"
This reverts commit a962a28afc.
2026-02-17 18:11:47 +00:00
Evan Quiney c8997217cf Revert "feat: better onboarding UX for new users (#1479)"
This reverts commit 490d2e46ba.
2026-02-17 18:02:32 +00:00
Alex Cheema 490d2e46ba feat: better onboarding UX for new users (#1479)
## Summary

- **Auto-open dashboard** in browser on first launch (uses
`~/.exo/.dashboard_opened` marker)
- **Welcome overlay** with "Choose a Model" CTA button when no model
instance is running
- **Tutorial progress messages** during model download → loading → ready
lifecycle stages
- **Fix conversation sidebar** text contrast — bumped to white text,
added active state background
- **Simplify technical jargon** — sharding/instance type/min nodes
hidden behind collapsible "Advanced Options" toggle; strategy display
hidden behind debug mode
- **Polished DMG installer** with drag-to-Applications layout, custom
branded background, and AppleScript-configured window positioning

## Test plan

- [ ] Launch exo for the first time (delete `~/.exo/.dashboard_opened`
to simulate) — browser should auto-open
- [ ] Verify welcome overlay appears on topology when no model is loaded
- [ ] Launch a model and verify download/loading/ready messages appear
in instance cards
- [ ] Check conversation sidebar text is readable (white on dark, yellow
when active)
- [ ] Verify "Advanced Options" toggle hides/shows sharding controls
- [ ] Build DMG with `packaging/dmg/create-dmg.sh` and verify
drag-to-Applications layout

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 17:52:49 +00:00
rltakashige facf2d4d03 Use custom fork that resolves GPU locks (#1489)
## Motivation

There is an issue on Macs that means that an explicit synchronization is
necessary for memory to be updated from L1 cache. This means that GPU
locks can occur when a spin wait does not see the updated timestamp.

## Changes

Updated in my own personal fork.

## Why It Works

https://github.com/ARM-software/acle/releases

## Test Plan

### Manual Testing
Tested manually that no GPU locks occur (even with multiple simultaneous
instances running) and that the performance differential is negligible
(267 vs 269 tps on Llama 3.2 1B at an approx 10k context.)


------------------------------------------------------
I have seen a GPU lock, specifically when sending a particularly large
chat completion while the model was loading. However, I have since been
unable to reproduce and this may be something I did wrong. Please do
create an issue and tag me if any GPU locks do occur.

---------

Co-authored-by: Jake Hillion <jake@hillion.co.uk>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 17:48:43 +00:00
Alex Cheema a962a28afc Add MetaInstance declarative layer (#1447)
## Motivation

Users currently manage instances directly, which means if a node
disconnects or connections break, the instance dies and nothing
recreates it. MetaInstance is a declarative primitive: "ensure an
instance matching these parameters always exists." The reconciler
watches for unhealthy or missing backing instances and re-places them
automatically.

## Changes

- **MetaInstance type** (`meta_instance.py`): declarative constraint
with `model_id`, `min_nodes`, optional `node_ids`, and `sharding`
- **Reconciler** (`reconcile.py`): `find_unsatisfied_meta_instances`
checks which MetaInstances lack a healthy backing instance,
`try_place_for_meta_instance` creates one
- **Master loop** (`main.py`): periodically reconciles unsatisfied
MetaInstances; immediate placement on `CreateMetaInstance` command
- **API** (`api.py`): `create_meta_instance` / `delete_meta_instance` /
`GET /meta_instances` endpoints; delete cascades to backing instances
with task cancellation
- **Binding via `meta_instance_id` on Instance** (`instances.py`): no
separate binding event or backing map — the instance carries its parent
MetaInstance ID directly, eliminating race conditions in the reconciler
- **Dashboard**: sidebar shows MetaInstances with their backing instance
status; orphan instances (created directly) still shown separately
- **Tests**: constraint matching, connection health, unsatisfied
detection, exclusive binding, cascade delete with task cancellation

### Recent improvements

- **fix: cancel active tasks on cascade delete** — `DeleteMetaInstance`
now emits `TaskStatusUpdated(Cancelled)` for any Pending/Running tasks
on backing instances before emitting `InstanceDeleted`. Previously,
cascade-deleting backing instances left orphaned task references in
state.
- **Lifecycle logging** — added `logger.info`/`logger.warning` for:
`CreateMetaInstance` (model, min_nodes, sharding), `DeleteMetaInstance`
(with cascade count), reconciler placement success/failure, and retry
decisions with attempt counts in `InstanceHealthReconciler`.
- **GET `/meta_instances` endpoint** — lists all meta-instances without
needing to fetch full state.
- **2 regression tests** — `test_cascade_delete_cancels_active_tasks`
and `test_cascade_delete_skips_completed_tasks` verify the
cascade-delete event sequence.

## Why It Works

Putting `meta_instance_id` on `BaseInstance` makes binding inherent to
instance creation. When the reconciler creates an instance for a
MetaInstance, it tags it via `model_copy`. When the instance is deleted,
the binding disappears with it. This avoids the two bugs that a separate
binding mechanism would introduce:
1. Stale exclusion sets — the reconciler loop can't accidentally bind
two MetaInstances to the same instance
2. Delete ordering race — no window between deleting an instance and its
binding where the reconciler could re-place

## Test Plan

### Manual Testing
<!-- Hardware: (e.g., MacBook Pro M1 Max 32GB, Mac Mini M2 16GB,
connected via Thunderbolt 4) -->
- Created MetaInstance via dashboard, verified instance placed
- Verified delete cascades (deleting MetaInstance removes backing
instance)
- Verified orphan instances still work independently

### Automated Testing
- 30 tests in `test_meta_instance_edge_cases.py`: lifecycle, retry
logic, error handling, concurrent operations, cascade delete with task
cancellation
- 24 tests in `test_reconcile.py`: constraint matching, connection
health (single/multi-node, edge removal, IP changes), unsatisfied
detection, exclusive binding, idempotency
- All 261 tests pass
- basedpyright 0 errors, ruff clean, dashboard builds

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:48:19 -08:00
Alex Cheema db79c350c1 Fix graceful process shutdown in macOS app (#1372)
## Motivation

Fixes #1370

When the macOS app stops exo, GPU/system memory isn't released. This
happens because:

1. The macOS app calls `process.terminate()` (SIGTERM) but the Python
process only registers a graceful shutdown handler for SIGINT, not
SIGTERM. SIGTERM's default Python behavior raises `SystemExit` which
bypasses the cleanup cascade (runner subprocess MLX cleanup via
`mx.clear_cache()`, channel closing, etc.).
2. The app doesn't wait for the process to actually finish cleanup — it
immediately nils out the process reference.

## Changes

**`src/exo/main.py`**: Register SIGTERM handler alongside SIGINT so the
graceful shutdown cascade (`Node.shutdown()` → cancel task group →
worker/runner cleanup → `mx.clear_cache()` + `gc.collect()`) runs
regardless of which signal is received.

**`app/EXO/EXO/ExoProcessController.swift`**: Replace immediate
`process.terminate()` with escalating shutdown per @Evanev7's
suggestion:
1. Send SIGINT via `process.interrupt()` — triggers the registered
Python handler for graceful cleanup
2. Wait up to 5 seconds for the process to exit
3. If still running, escalate to SIGTERM via `process.terminate()`
4. Wait up to 3 seconds
5. If still running, force kill via SIGKILL

The escalation runs in a detached `Task` so the UI updates immediately
(status → stopped) without blocking.

## Why It Works

The root cause is that SIGTERM wasn't triggering the graceful shutdown
path. By registering a SIGTERM handler in Python and sending SIGINT
first from the macOS app, the process gets a chance to run the full
cleanup cascade: cancelling the task group, shutting down runners (which
call `del model; mx.clear_cache(); gc.collect()`), closing channels, and
flushing logs. The escalation to SIGTERM and SIGKILL ensures the process
always terminates even if graceful shutdown hangs.

## Test Plan

### Manual Testing
<!-- Hardware: Mac Studio M4 Max 128GB -->
- Start exo via macOS app, load a model, run inference
- Stop via the toggle switch, verify memory is released without
requiring a system restart
- Test rapid stop/start (restart) to ensure no race conditions

### Automated Testing
- `uv run basedpyright` — 0 errors
- `uv run ruff check` — passes
- `nix fmt` — no changes

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Evan Quiney <evanev7@gmail.com>
2026-02-17 09:03:54 -08:00
Alex Cheema d6301ed593 dashboard: redesign downloads page as model×node table (#1465)
## Motivation

The current downloads page uses a node-centric card grid layout that is
messy and hard to read — the same model across different nodes appears
in separate cards, and deep nesting wastes space. This makes it
difficult to quickly see which models are on which nodes.

## Changes

Rewrote the downloads page
(`dashboard/src/routes/downloads/+page.svelte`) from a card grid to a
clean table layout:

- **Rows** = models (unique across all nodes)
- **Columns** = nodes (with disk free shown in header)
- **Cells** show status at a glance:
  -  Green checkmark + size for completed downloads
  - 🟡 Yellow percentage + mini progress bar + speed for active downloads
  - `...` for pending downloads
  -  Red X for failed downloads
  - `--` for models not present on a node
- Delete/download action buttons appear on row hover
- Model name column is sticky on horizontal scroll (for many-node
clusters)
- Models sorted by number of nodes with completed downloads
- Imported shared utilities from `$lib/utils/downloads` instead of
inline re-implementations

### Backend: model directory in download events

- Added `model_directory` field to `BaseDownloadProgress` so all
download status events include the on-disk path
- Added `_model_dir()` helper to `DownloadCoordinator` to compute the
path from `EXO_MODELS_DIR`
- Dashboard uses this to show file location and enable "open in Finder"
for completed downloads

### Info modal

- Clicking a model name opens an info modal showing card details
(family, quantization, capabilities, storage size, layer count, tensor
parallelism support)

### Other fixes

- Fixed model name truncation in the table
- Excluded `tests/start_distributed_test.py` from pytest collection (CLI
script that calls `sys.exit()` at import time)

## Test Plan

- [x] `uv run basedpyright` — 0 errors
- [x] `uv run ruff check` — all passed
- [x] `nix fmt` — clean
- [x] `uv run pytest` — 188 passed, 1 skipped

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 14:31:47 +00:00
Evan Quiney 6d1ca6689b don't time out node identities (#1493)
currently nodes leaving and rejoining the cluster can lose their identity. We have no need to delete this data on node timing out, so let's just persist it.
2026-02-17 11:48:28 +00:00
1745 changed files with 144833 additions and 5002 deletions
+20
View File
@@ -0,0 +1,20 @@
from enum import Enum
class HarmonyEncodingName(Enum):
HARMONY_GPT_OSS = ...
class HarmonyEncoding: ...
class HarmonyError(Exception): ...
class Role(Enum):
ASSISTANT = ...
class StreamableParser:
last_content_delta: str
current_channel: str | None
current_recipient: str | None
def __init__(self, encoding: HarmonyEncoding, role: Role = ...) -> None: ...
def process(self, token_id: int) -> None: ...
def load_harmony_encoding(name: HarmonyEncodingName) -> HarmonyEncoding: ...
+8
View File
@@ -0,0 +1,8 @@
from torch import backends as backends
from torch import cuda as cuda
from torch import distributed as distributed
__version__: str
class version:
cuda: str
@@ -0,0 +1 @@
from torch.backends import cuda as cuda
@@ -0,0 +1 @@
def is_built() -> bool: ...
+8
View File
@@ -0,0 +1,8 @@
class _DeviceProperties:
total_memory: int
def is_available() -> bool: ...
def get_device_name(device: int) -> str: ...
def get_device_properties(device: int) -> _DeviceProperties: ...
def empty_cache() -> None: ...
def mem_get_info() -> tuple[int, int]: ...
@@ -0,0 +1,2 @@
def is_initialized() -> bool: ...
def destroy_process_group() -> None: ...
View File
+56
View File
@@ -0,0 +1,56 @@
from .version import __version__ as __version__, __version_tuple__ as __version_tuple__
from vllm.engine.arg_utils import (
AsyncEngineArgs as AsyncEngineArgs,
EngineArgs as EngineArgs,
)
from vllm.engine.async_llm_engine import AsyncLLMEngine as AsyncLLMEngine
from vllm.engine.llm_engine import LLMEngine as LLMEngine
from vllm.entrypoints.llm import LLM as LLM
from vllm.inputs import (
PromptType as PromptType,
TextPrompt as TextPrompt,
TokensPrompt as TokensPrompt,
)
from vllm.model_executor.models import ModelRegistry as ModelRegistry
from vllm.outputs import (
ClassificationOutput as ClassificationOutput,
ClassificationRequestOutput as ClassificationRequestOutput,
CompletionOutput as CompletionOutput,
EmbeddingOutput as EmbeddingOutput,
EmbeddingRequestOutput as EmbeddingRequestOutput,
PoolingOutput as PoolingOutput,
PoolingRequestOutput as PoolingRequestOutput,
RequestOutput as RequestOutput,
ScoringOutput as ScoringOutput,
ScoringRequestOutput as ScoringRequestOutput,
)
from vllm.pooling_params import PoolingParams as PoolingParams
from vllm.sampling_params import SamplingParams as SamplingParams
from vllm.v1.executor.ray_utils import initialize_ray_cluster as initialize_ray_cluster
__all__ = [
"__version__",
"__version_tuple__",
"LLM",
"ModelRegistry",
"PromptType",
"TextPrompt",
"TokensPrompt",
"SamplingParams",
"RequestOutput",
"CompletionOutput",
"PoolingOutput",
"PoolingRequestOutput",
"EmbeddingOutput",
"EmbeddingRequestOutput",
"ClassificationOutput",
"ClassificationRequestOutput",
"ScoringOutput",
"ScoringRequestOutput",
"LLMEngine",
"EngineArgs",
"AsyncLLMEngine",
"AsyncEngineArgs",
"initialize_ray_cluster",
"PoolingParams",
]
+341
View File
@@ -0,0 +1,341 @@
import torch
from _typeshed import Incomplete
from collections.abc import Callable as Callable
from torch._ops import OpOverload as OpOverload
from vllm.platforms import current_platform as current_platform
from vllm.utils.torch_utils import (
direct_register_custom_op as direct_register_custom_op,
)
from vllm.v1.attention.ops.rocm_aiter_mla_sparse import (
rocm_aiter_sparse_attn_indexer as rocm_aiter_sparse_attn_indexer,
rocm_aiter_sparse_attn_indexer_fake as rocm_aiter_sparse_attn_indexer_fake,
)
FP8_DTYPE: Incomplete
def is_aiter_found() -> bool: ...
IS_AITER_FOUND: Incomplete
def is_aiter_found_and_supported() -> bool: ...
def if_aiter_supported(func: Callable) -> Callable: ...
class rocm_aiter_ops:
@classmethod
def refresh_env_variables(cls) -> None: ...
@staticmethod
def get_aiter_activation_type(activation_str: str): ...
@staticmethod
def get_aiter_quant_type(quant_type_str: str): ...
@classmethod
@if_aiter_supported
def is_enabled(cls) -> bool: ...
@classmethod
@if_aiter_supported
def is_linear_enabled(cls) -> bool: ...
@classmethod
@if_aiter_supported
def is_linear_fp8_enabled(cls) -> bool: ...
@classmethod
@if_aiter_supported
def is_rmsnorm_enabled(cls) -> bool: ...
@classmethod
@if_aiter_supported
def is_fused_moe_enabled(cls) -> bool: ...
@classmethod
@if_aiter_supported
def is_fusion_moe_shared_experts_enabled(cls) -> bool: ...
@classmethod
@if_aiter_supported
def is_mla_enabled(cls) -> bool: ...
@classmethod
@if_aiter_supported
def is_mha_enabled(cls) -> bool: ...
@classmethod
@if_aiter_supported
def is_shuffle_kv_cache_enabled(cls) -> bool: ...
@classmethod
@if_aiter_supported
def is_triton_unified_attn_enabled(cls) -> bool: ...
@classmethod
@if_aiter_supported
def is_fp8bmm_enabled(cls) -> bool: ...
@classmethod
@if_aiter_supported
def is_fp4bmm_enabled(cls) -> bool: ...
@classmethod
@if_aiter_supported
def is_asm_fp4_gemm_dynamic_quant_enabled(cls) -> bool: ...
@classmethod
@if_aiter_supported
def is_triton_rotary_embed_enabled(cls) -> bool: ...
@classmethod
@if_aiter_supported
def is_triton_gemm_enabled(cls) -> bool: ...
@staticmethod
@if_aiter_supported
def register_ops_once() -> None: ...
@staticmethod
def get_rmsnorm_fused_add_op() -> OpOverload: ...
@staticmethod
def get_rmsnorm_op() -> OpOverload: ...
@staticmethod
def get_rmsnorm_fused_add_dynamic_quant_op() -> OpOverload: ...
@staticmethod
def get_rmsnorm_fused_dynamic_quant_op() -> OpOverload: ...
@staticmethod
def get_rmsnorm_group_fused_quant_op() -> OpOverload: ...
@staticmethod
def get_rmsnorm_group_add_fused_quant_op() -> OpOverload: ...
@staticmethod
def get_per_token_quant_op() -> OpOverload: ...
@staticmethod
def get_group_quant_op() -> OpOverload: ...
@staticmethod
def get_act_mul_fused_fp8_group_quant_op() -> OpOverload: ...
@staticmethod
def get_triton_add_rmsnorm_pad_op() -> OpOverload: ...
@staticmethod
def get_triton_rotary_embedding_op() -> OpOverload: ...
@staticmethod
def rms_norm(
x: torch.Tensor, weight: torch.Tensor, variance_epsilon: float
) -> torch.Tensor: ...
@staticmethod
def rms_norm2d_with_add(
x: torch.Tensor,
residual: torch.Tensor,
weight: torch.Tensor,
variance_epsilon: float,
) -> tuple[torch.Tensor, torch.Tensor]: ...
@staticmethod
def gemm_a8w8(
A: torch.Tensor,
B: torch.Tensor,
As: torch.Tensor,
Bs: torch.Tensor,
bias: torch.Tensor | None = None,
output_dtype: torch.dtype = ...,
) -> torch.Tensor: ...
@staticmethod
def triton_gemm_a8w8_blockscale(
A: torch.Tensor,
B: torch.Tensor,
As: torch.Tensor,
Bs: torch.Tensor,
block_size: list[int],
output_dtype: torch.dtype = ...,
) -> torch.Tensor: ...
@staticmethod
def gemm_a8w8_blockscale(
A: torch.Tensor,
B: torch.Tensor,
As: torch.Tensor,
Bs: torch.Tensor,
block_size: list[int],
output_dtype: torch.dtype = ...,
) -> torch.Tensor: ...
@staticmethod
def fused_moe(
hidden_states: torch.Tensor,
w1: torch.Tensor,
w2: torch.Tensor,
topk_weight: torch.Tensor,
topk_ids: torch.Tensor,
expert_mask: torch.Tensor | None = None,
activation_method: int = 0,
quant_method: int = 0,
doweight_stage1: bool = False,
w1_scale: torch.Tensor | None = None,
w2_scale: torch.Tensor | None = None,
a1_scale: torch.Tensor | None = None,
a2_scale: torch.Tensor | None = None,
num_local_tokens: torch.Tensor | None = None,
output_dtype: torch.dtype | None = None,
hidden_pad: int = 0,
intermediate_pad: int = 0,
bias1: torch.Tensor | None = None,
bias2: torch.Tensor | None = None,
) -> torch.Tensor: ...
@staticmethod
def asm_moe_tkw1(
hidden_states: torch.Tensor,
w1: torch.Tensor,
w2: torch.Tensor,
topk_weights: torch.Tensor,
topk_ids: torch.Tensor,
fc1_scale: torch.Tensor | None = None,
fc2_scale: torch.Tensor | None = None,
fc1_smooth_scale: torch.Tensor | None = None,
fc2_smooth_scale: torch.Tensor | None = None,
a16: bool = False,
per_tensor_quant_scale: torch.Tensor | None = None,
expert_mask: torch.Tensor | None = None,
activation_method: int = 0,
) -> torch.Tensor: ...
@staticmethod
def topk_softmax(
topk_weights: torch.Tensor,
topk_indices: torch.Tensor,
token_expert_indices: torch.Tensor,
gating_output: torch.Tensor,
renormalize: bool,
) -> tuple[torch.Tensor, ...]: ...
@staticmethod
def topk_sigmoid(
topk_weights: torch.Tensor,
topk_indices: torch.Tensor,
token_expert_indices: torch.Tensor,
gating_output: torch.Tensor,
renormalize: bool,
) -> tuple[torch.Tensor, ...]: ...
@staticmethod
def biased_grouped_topk(
gating_output: torch.Tensor,
correction_bias: torch.Tensor,
topk_weights: torch.Tensor,
topk_ids: torch.Tensor,
num_expert_group: int,
topk_group: int,
need_renorm: bool,
routed_scaling_factor: float = 1.0,
) -> None: ...
@staticmethod
def grouped_topk(
gating_output: torch.Tensor,
topk_weights: torch.Tensor,
topk_ids: torch.Tensor,
num_expert_group: int,
topk_group: int,
need_renorm: bool,
scoring_func: str = "softmax",
routed_scaling_factor: float = 1.0,
) -> None: ...
@staticmethod
def fused_topk(
x: torch.Tensor, router_logits: torch.Tensor, top_k: int, gate_up: bool
) -> tuple[torch.Tensor, torch.Tensor]: ...
@staticmethod
def mla_decode_fwd(
q: torch.Tensor,
kv_buffer: torch.Tensor,
o: torch.Tensor,
sm_scale: float,
qo_indptr: torch.Tensor,
max_seqlen_qo: int,
kv_indptr: torch.Tensor | None = None,
kv_indices: torch.Tensor | None = None,
kv_last_page_lens: torch.Tensor | None = None,
logit_cap: float = 0.0,
q_scale: torch.Tensor | None = None,
kv_scale: torch.Tensor | None = None,
): ...
@staticmethod
def per_tensor_quant(
x: torch.Tensor, quant_dtype: torch.dtype, scale: torch.Tensor | None = None
) -> tuple[torch.Tensor, torch.Tensor]: ...
@staticmethod
def per_token_quant(
x: torch.Tensor, quant_dtype: torch.dtype, scale: torch.Tensor | None = None
) -> tuple[torch.Tensor, torch.Tensor]: ...
@staticmethod
def triton_fp4_gemm_dynamic_qaunt(
x: torch.Tensor,
weight: torch.Tensor,
weight_scale: torch.Tensor,
out_dtype: torch.dtype | None = ...,
x_scales: torch.Tensor | None = None,
) -> torch.Tensor: ...
@staticmethod
def triton_rope_and_cache(
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
positions: torch.Tensor,
cos_sin_cache: torch.Tensor,
is_neox: bool,
key_cache: torch.Tensor,
value_cache: torch.Tensor,
layer_slot_mapping: torch.Tensor,
k_scale: torch.Tensor,
v_scale: torch.Tensor,
flash_layout: bool,
apply_scale: bool,
): ...
@staticmethod
def batched_gemm_a16wfp4(
X: torch.Tensor,
W: torch.Tensor,
w_scale: torch.Tensor,
Y: torch.Tensor,
transpose_bm: bool | None = False,
prequant: bool | None = False,
y_scale: torch.Tensor | None = None,
) -> torch.Tensor: ...
@staticmethod
def triton_fp8_bmm(
X: torch.Tensor,
WQ: torch.Tensor,
w_scale: torch.Tensor,
group_size: int = 128,
bias: torch.Tensor | None = None,
dtype: torch.dtype | None = ...,
splitK: int | None = None,
YQ: torch.Tensor | None = None,
transpose_bm: bool | None = False,
config: dict | None = None,
) -> torch.Tensor: ...
@staticmethod
def group_fp8_quant(
input_2d: torch.Tensor, group_size: int = 128
) -> tuple[torch.Tensor, torch.Tensor]: ...
@staticmethod
def is_triton_gemm_w8a8_tuned(n: int, k: int) -> bool: ...
@staticmethod
def is_triton_gemm_afp4wfp4_presh_ws_tuned(n: int, k: int) -> bool: ...
@staticmethod
def shuffle_weight(
self, tensor: torch.Tensor, layout: tuple[int, int] = (16, 16)
) -> torch.Tensor: ...
@staticmethod
def shuffle_weight_a16w4(
tensor: torch.Tensor, nLane: int, gate_up: bool
) -> torch.Tensor: ...
@staticmethod
def shuffle_scale_a16w4(
tensor: torch.Tensor, num_experts: int, gate_up: bool
) -> torch.Tensor: ...
@staticmethod
def shuffle_weights(
*tensors: torch.Tensor, layout: tuple[int, int] = (16, 16)
) -> tuple[torch.Tensor, ...]: ...
@staticmethod
def flash_attn_varlen_func(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
cu_seqlens_q: torch.Tensor,
cu_seqlens_k: torch.Tensor,
max_seqlen_q: int,
max_seqlen_k: int,
min_seqlen_q: int | None = None,
dropout_p: float = 0.0,
softmax_scale: float | None = None,
causal: bool = False,
window_size: tuple[int, int] | None = None,
alibi_slopes: torch.Tensor | None = None,
return_lse: bool = False,
out: torch.Tensor | None = None,
): ...
@staticmethod
def pa_fwd_asm(
Q: torch.Tensor,
K: torch.Tensor,
V: torch.Tensor,
block_tables: torch.Tensor,
context_lens: torch.Tensor,
block_tables_stride0: int,
K_QScale: torch.Tensor,
V_QScale: torch.Tensor,
out_: torch.Tensor,
): ...
File diff suppressed because it is too large Load Diff
View File
+14
View File
@@ -0,0 +1,14 @@
import torch
from collections.abc import Callable as Callable
from torch._dynamo import disable as _dynamo_disable
@_dynamo_disable
def is_oink_available_for_device(device_index: int) -> bool: ...
def has_fused_add_rms_norm() -> bool: ...
def rmsnorm(x: torch.Tensor, weight: torch.Tensor, eps: float) -> torch.Tensor: ...
def fused_add_rms_norm_(
x: torch.Tensor, residual: torch.Tensor, weight: torch.Tensor, eps: float
) -> None: ...
def fused_add_rms_norm(
x: torch.Tensor, residual: torch.Tensor, weight: torch.Tensor, eps: float
) -> tuple[torch.Tensor, torch.Tensor]: ...
+17
View File
@@ -0,0 +1,17 @@
__all__ = [
"__version__",
"__version_tuple__",
"version",
"version_tuple",
"__commit_id__",
"commit_id",
]
VERSION_TUPLE = tuple[int | str, ...]
COMMIT_ID = str | None
version: str
__version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE
commit_id: COMMIT_ID
__commit_id__: COMMIT_ID
+59
View File
@@ -0,0 +1,59 @@
import torch
from _typeshed import Incomplete
from vllm.logger import init_logger as init_logger
logger: Incomplete
def register_fake(fn): ...
class xpu_ops:
@staticmethod
def flash_attn_varlen_func(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
cu_seqlens_q: torch.Tensor,
max_seqlen_q: int,
max_seqlen_k: int,
softmax_scale: float | None = None,
causal: bool = False,
out: torch.Tensor | None = None,
block_table: torch.Tensor | None = None,
alibi_slopes: torch.Tensor | None = None,
window_size: list[int] | None = None,
softcap: float | None = 0.0,
seqused_k: torch.Tensor | None = None,
cu_seqlens_k: torch.Tensor | None = None,
dropout_p: float = 0.0,
scheduler_metadata=None,
fa_version: int = 2,
q_descale=None,
k_descale=None,
v_descale=None,
num_splits: int = 0,
return_softmax_lse: bool | None = False,
s_aux: torch.Tensor | None = None,
): ...
@staticmethod
def get_scheduler_metadata(
batch_size,
max_seqlen_q,
max_seqlen_k,
num_heads_q,
num_heads_kv,
headdim,
cache_seqlens: torch.Tensor,
qkv_dtype=...,
headdim_v=None,
cu_seqlens_q: torch.Tensor | None = None,
cu_seqlens_k_new: torch.Tensor | None = None,
cache_leftpad: torch.Tensor | None = None,
page_size: int | None = None,
max_seqlen_k_new: int = 0,
causal: bool = False,
window_size=(-1, -1),
has_softcap: bool = False,
num_splits: int = 0,
pack_gqa=None,
sm_margin: int = 0,
) -> None: ...
+23
View File
@@ -0,0 +1,23 @@
import numpy.typing as npt
from .base import (
VLLM_S3_BUCKET_URL as VLLM_S3_BUCKET_URL,
get_vllm_public_assets as get_vllm_public_assets,
)
from _typeshed import Incomplete
from dataclasses import dataclass
from pathlib import Path
from vllm.utils.import_utils import PlaceholderModule as PlaceholderModule
ASSET_DIR: str
AudioAssetName: Incomplete
@dataclass(frozen=True)
class AudioAsset:
name: AudioAssetName
@property
def filename(self) -> str: ...
@property
def audio_and_sample_rate(self) -> tuple[npt.NDArray, float]: ...
def get_local_path(self) -> Path: ...
@property
def url(self) -> str: ...
+9
View File
@@ -0,0 +1,9 @@
from functools import lru_cache
from pathlib import Path
from vllm.connections import global_http_connection as global_http_connection
VLLM_S3_BUCKET_URL: str
def get_cache_dir() -> Path: ...
@lru_cache
def get_vllm_public_assets(filename: str, s3_prefix: str | None = None) -> Path: ...
+20
View File
@@ -0,0 +1,20 @@
import torch
from .base import get_vllm_public_assets as get_vllm_public_assets
from PIL import Image
from _typeshed import Incomplete
from dataclasses import dataclass
from pathlib import Path
VLM_IMAGES_DIR: str
ImageAssetName: Incomplete
@dataclass(frozen=True)
class ImageAsset:
name: ImageAssetName
def get_path(self, ext: str) -> Path: ...
@property
def pil_image(self) -> Image.Image: ...
def pil_image_ext(self, ext: str) -> Image.Image: ...
@property
def image_embeds(self) -> torch.Tensor: ...
def read_bytes(self, ext: str) -> bytes: ...
+32
View File
@@ -0,0 +1,32 @@
import numpy.typing as npt
from .base import get_cache_dir as get_cache_dir
from PIL import Image
from _typeshed import Incomplete
from dataclasses import dataclass
from functools import lru_cache
from typing import Any
from vllm.utils.import_utils import PlaceholderModule as PlaceholderModule
@lru_cache
def download_video_asset(filename: str) -> str: ...
def video_to_ndarrays(path: str, num_frames: int = -1) -> npt.NDArray: ...
def video_to_pil_images_list(path: str, num_frames: int = -1) -> list[Image.Image]: ...
def video_get_metadata(path: str, num_frames: int = -1) -> dict[str, Any]: ...
VideoAssetName: Incomplete
@dataclass(frozen=True)
class VideoAsset:
name: VideoAssetName
num_frames: int = ...
@property
def filename(self) -> str: ...
@property
def video_path(self) -> str: ...
@property
def pil_images(self) -> list[Image.Image]: ...
@property
def np_ndarrays(self) -> npt.NDArray: ...
@property
def metadata(self) -> dict[str, Any]: ...
def get_audio(self, sampling_rate: float | None = None) -> npt.NDArray: ...
+48
View File
@@ -0,0 +1,48 @@
from dataclasses import dataclass
from vllm.inputs import (
EncoderDecoderInputs as EncoderDecoderInputs,
TokenInputs as TokenInputs,
token_inputs as token_inputs,
)
from vllm.inputs.data import DecoderInputs as DecoderInputs
from vllm.logprobs import Logprob as Logprob
from vllm.lora.request import LoRARequest as LoRARequest
from vllm.multimodal.inputs import (
MultiModalInputs as MultiModalInputs,
mm_inputs as mm_inputs,
)
@dataclass
class BeamSearchSequence:
orig_prompt: TokenInputs | MultiModalInputs | EncoderDecoderInputs
tokens: list[int]
logprobs: list[dict[int, Logprob]]
lora_request: LoRARequest | None = ...
cum_logprob: float = ...
text: str | None = ...
finish_reason: str | None = ...
stop_reason: int | str | None = ...
def get_prompt(self): ...
@dataclass
class BeamSearchOutput:
sequences: list[BeamSearchSequence]
class BeamSearchInstance:
beams: list[BeamSearchSequence]
completed: list[BeamSearchSequence]
def __init__(
self,
prompt: TokenInputs | MultiModalInputs | EncoderDecoderInputs,
lora_request: LoRARequest | None = None,
logprobs: list[dict[int, Logprob]] | None = None,
**kwargs,
) -> None: ...
def get_beam_search_score(
tokens: list[int],
cumulative_logprob: float,
eos_token_id: int,
length_penalty: float = 1.0,
) -> float: ...
def create_sort_beams_key_function(eos_token_id: int, length_penalty: float): ...
+516
View File
@@ -0,0 +1,516 @@
import abc
import argparse
import numpy as np
from PIL import Image
from _typeshed import Incomplete
from abc import ABC, abstractmethod
from collections.abc import Callable as Callable, Iterator, Mapping
from dataclasses import dataclass
from functools import cache
from typing import Any
from vllm.lora.request import LoRARequest as LoRARequest
from vllm.lora.utils import get_adapter_absolute_path as get_adapter_absolute_path
from vllm.multimodal import MultiModalDataDict as MultiModalDataDict
from vllm.multimodal.image import convert_image_mode as convert_image_mode
from vllm.tokenizers import TokenizerLike as TokenizerLike
from vllm.utils.argparse_utils import FlexibleArgumentParser as FlexibleArgumentParser
from vllm.utils.import_utils import PlaceholderModule as PlaceholderModule
datasets: Incomplete
logger: Incomplete
DEFAULT_NUM_PROMPTS: int
@dataclass
class SampleRequest:
prompt: str | list[str]
prompt_len: int
expected_output_len: int
multi_modal_data: MultiModalDataDict | dict | list[dict] | None = ...
lora_request: LoRARequest | None = ...
request_id: str | None = ...
class BenchmarkDataset(ABC, metaclass=abc.ABCMeta):
DEFAULT_SEED: int
IS_MULTIMODAL: bool
dataset_path: Incomplete
random_seed: Incomplete
disable_shuffle: Incomplete
data: Incomplete
def __init__(
self,
dataset_path: str | None = None,
random_seed: int = ...,
disable_shuffle: bool = False,
**kwargs,
) -> None: ...
def apply_multimodal_chat_transformation(
self,
prompt: str,
mm_content: MultiModalDataDict | dict | list[dict] | None = None,
) -> list[dict]: ...
def load_data(self) -> None: ...
def get_random_lora_request(
self, max_loras: int | None = None, lora_path: str | None = None
) -> LoRARequest | None: ...
@abstractmethod
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
request_id_prefix: str = "",
no_oversample: bool = False,
) -> list[SampleRequest]: ...
def maybe_oversample_requests(
self,
requests: list[SampleRequest],
num_requests: int,
request_id_prefix: str = "",
no_oversample: bool = False,
) -> None: ...
def is_valid_sequence(
prompt_len: int,
output_len: int,
min_len: int = 4,
max_prompt_len: int = 1024,
max_total_len: int = 2048,
skip_min_output_len_check: bool = False,
) -> bool: ...
@cache
def lora_path_on_disk(lora_path: str) -> str: ...
lora_tokenizer_cache: dict[int, TokenizerLike]
def process_image(image: Any) -> Mapping[str, Any]: ...
def process_video(video: Any) -> Mapping[str, Any]: ...
def gen_prompt_decode_to_target_len(
tokenizer: TokenizerLike,
token_sequence: list[int],
target_token_len: int,
max_retry: int = 10,
add_special_tokens: bool = False,
rng: np.random.Generator | None = None,
) -> tuple[str, list[int], int]: ...
class RandomDataset(BenchmarkDataset):
DEFAULT_PREFIX_LEN: int
DEFAULT_RANGE_RATIO: float
DEFAULT_INPUT_LEN: int
DEFAULT_OUTPUT_LEN: int
def __init__(self, **kwargs) -> None: ...
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
request_id_prefix: str = "",
no_oversample: bool = False,
prefix_len: int = ...,
range_ratio: float = ...,
input_len: int = ...,
output_len: int = ...,
batchsize: int = 1,
**kwargs,
) -> list[SampleRequest]: ...
def get_prefix(
self, tokenizer: TokenizerLike, allowed_tokens: np.ndarray, prefix_len: int
) -> list[int]: ...
def get_sampling_params(
self,
num_requests: int,
range_ratio: float,
input_len: int,
output_len: int,
tokenizer: TokenizerLike,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]: ...
def generate_token_sequence(
self,
*,
tokenizer: TokenizerLike,
prefix_token_ids: list[int],
prefix_len: int,
vocab_size: int,
input_len: int,
offset: int,
index: int,
allowed_tokens: np.ndarray,
) -> tuple[str, int, int]: ...
class RandomDatasetForReranking(RandomDataset):
def __init__(self, **kwargs) -> None: ...
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
request_id_prefix: str = "",
range_ratio: float = ...,
input_len: int = ...,
batchsize: int = 1,
is_reranker: bool = True,
**kwargs,
) -> list[SampleRequest]: ...
class RandomMultiModalDataset(RandomDataset):
IS_MULTIMODAL: bool
DEFAULT_LIMIT_MM_PER_PROMPT: Incomplete
DEFAULT_BASE_ITEMS_PER_REQUEST: int
DEFAULT_NUM_MM_ITEMS_RANGE_RATIO: float
DEFAULT_MM_ITEM_BUCKET_CONFIG: Incomplete
DEFAULT_ENABLE_MULTIMODAL_CHAT: bool
def __init__(self, **kwargs) -> None: ...
def generate_synthetic_image(self, width: int, height: int) -> Image.Image: ...
def generate_synthetic_video(
self, width: int, height: int, num_frames: int
) -> dict: ...
def map_config_to_modality(self, config: tuple[int, int, int]) -> str: ...
def normalize_bucket_config(
self, bucket_config: dict[tuple[int, int, int], float]
) -> dict[tuple[int, int, int], float]: ...
def generate_mm_item(
self, mm_item_config: tuple[int, int, int]
) -> Mapping[str, Any]: ...
def get_mm_item_sampling_params(
self,
base_items_per_request: int,
num_mm_items_range_ratio: float,
limit_mm_per_prompt: dict[str, int],
bucket_config: dict[tuple[int, int, int], float],
) -> tuple[int, int, dict[str, int], dict[tuple[int, int, int], float]]: ...
def get_mm_item_iterator(
self,
min_num_mm_items: int,
max_num_mm_items: int,
bucket_config: dict[tuple[int, int, int], float],
limit_mm_per_prompt: dict[str, int],
) -> Iterator[tuple[int, int, int]]: ...
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
request_id_prefix: str = "",
no_oversample: bool = False,
prefix_len: int = ...,
range_ratio: float = ...,
input_len: int = ...,
output_len: int = ...,
limit_mm_per_prompt: dict[str, int] = ...,
base_items_per_request: int = ...,
num_mm_items_range_ratio: float = ...,
bucket_config: dict[tuple[int, int, int], float] = ...,
enable_multimodal_chat: bool = ...,
**kwargs,
) -> list[SampleRequest]: ...
class ShareGPTDataset(BenchmarkDataset):
def __init__(self, **kwargs) -> None: ...
data: Incomplete
def load_data(self) -> None: ...
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
lora_path: str | None = None,
max_loras: int | None = None,
output_len: int | None = None,
enable_multimodal_chat: bool = False,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
) -> list: ...
class _ValidateDatasetArgs(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None) -> None: ...
def add_dataset_parser(parser: FlexibleArgumentParser): ...
def add_random_dataset_base_args(
parser_or_group: FlexibleArgumentParser | argparse._ArgumentGroup,
) -> None: ...
def add_random_multimodal_dataset_args(
parser_or_group: FlexibleArgumentParser | argparse._ArgumentGroup,
) -> None: ...
def get_samples(args, tokenizer: TokenizerLike) -> list[SampleRequest]: ...
class CustomDataset(BenchmarkDataset):
def __init__(self, **kwargs) -> None: ...
data: Incomplete
def load_data(self) -> None: ...
num_available_samples: Incomplete
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
lora_path: str | None = None,
max_loras: int | None = None,
output_len: int | None = None,
enable_multimodal_chat: bool = False,
skip_chat_template: bool = False,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
) -> list: ...
class CustomMMDataset(CustomDataset):
IS_MULTIMODAL: bool
num_available_samples: Incomplete
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
output_len: int | None = None,
enable_multimodal_chat: bool = False,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
) -> list: ...
class SpecBench(CustomDataset):
category: Incomplete
def __init__(self, **kwargs) -> None: ...
data: Incomplete
def load_data(self) -> None: ...
def sample(self, **kwargs) -> list: ...
class SonnetDataset(BenchmarkDataset):
DEFAULT_PREFIX_LEN: int
DEFAULT_INPUT_LEN: int
DEFAULT_OUTPUT_LEN: int
def __init__(self, **kwargs) -> None: ...
data: Incomplete
def load_data(self) -> None: ...
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
prefix_len: int = ...,
input_len: int = ...,
output_len: int = ...,
return_prompt_formatted: bool = False,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
) -> list: ...
class BurstGPTDataset(BenchmarkDataset):
def __init__(self, **kwargs) -> None: ...
data: Incomplete
def load_data(self) -> None: ...
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
max_loras: int | None = None,
lora_path: str | None = None,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
) -> list[SampleRequest]: ...
class HuggingFaceDataset(BenchmarkDataset, metaclass=abc.ABCMeta):
SUPPORTED_DATASET_PATHS: set[str] | dict[str, Callable]
dataset_split: Incomplete
dataset_subset: Incomplete
load_stream: Incomplete
hf_name: Incomplete
trust_remote_code: Incomplete
def __init__(
self,
dataset_path: str,
dataset_split: str,
no_stream: bool = False,
dataset_subset: str | None = None,
hf_name: str | None = None,
trust_remote_code: bool = False,
**kwargs,
) -> None: ...
data: Incomplete
def load_data(self) -> None: ...
class ConversationDataset(HuggingFaceDataset):
SUPPORTED_DATASET_PATHS: Incomplete
IS_MULTIMODAL: bool
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
output_len: int | None = None,
enable_multimodal_chat: bool = False,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
) -> list: ...
class MultiModalConversationDataset(HuggingFaceDataset):
SUPPORTED_DATASET_PATHS: Incomplete
IS_MULTIMODAL: bool
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
output_len: int | None = None,
enable_multimodal_chat: bool = False,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
) -> list: ...
class VisionArenaDataset(HuggingFaceDataset):
DEFAULT_OUTPUT_LEN: int
SUPPORTED_DATASET_PATHS: Incomplete
IS_MULTIMODAL: bool
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
output_len: int | None = None,
enable_multimodal_chat: bool = False,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
) -> list: ...
class MMVUDataset(HuggingFaceDataset):
DEFAULT_OUTPUT_LEN: int
SUPPORTED_DATASET_PATHS: Incomplete
def __init__(self, **kwargs) -> None: ...
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
output_len: int | None = None,
enable_multimodal_chat: bool = False,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
) -> list: ...
class InstructCoderDataset(HuggingFaceDataset):
DEFAULT_OUTPUT_LEN: int
SUPPORTED_DATASET_PATHS: Incomplete
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
output_len: int | None = None,
enable_multimodal_chat: bool = False,
skip_chat_template: bool = False,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
) -> list[SampleRequest]: ...
def sample_prompts(self, n: int) -> Iterator[str]: ...
class MTBenchDataset(HuggingFaceDataset):
DEFAULT_OUTPUT_LEN: int
SUPPORTED_DATASET_PATHS: Incomplete
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
output_len: int | None = None,
enable_multimodal_chat: bool = False,
skip_chat_template: bool = False,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
) -> list: ...
class BlazeditDataset(HuggingFaceDataset):
DEFAULT_OUTPUT_LEN: int
SUPPORTED_DATASET_PATHS: Incomplete
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
output_len: int | None = None,
skip_chat_template: bool = False,
request_id_prefix: str = "",
no_oversample: bool = False,
min_distance: float = 0.0,
max_distance: float = 1.0,
**kwargs,
) -> list: ...
class AIMODataset(HuggingFaceDataset):
SUPPORTED_DATASET_PATHS: Incomplete
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
output_len: int | None = None,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
) -> list: ...
zeta_prompt: str
class NextEditPredictionDataset(HuggingFaceDataset):
SUPPORTED_DATASET_PATHS: Incomplete
MAPPING_PROMPT_FUNCS: Incomplete
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
): ...
class ASRDataset(HuggingFaceDataset):
SUPPORTED_DATASET_PATHS: Incomplete
DEFAULT_OUTPUT_LEN: int
IS_MULTIMODAL: bool
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
output_len: int | None = None,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
) -> list: ...
class MLPerfDataset(HuggingFaceDataset):
SUPPORTED_DATASET_PATHS: Incomplete
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
output_len: int | None = None,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
) -> list[SampleRequest]: ...
class PrefixRepetitionRandomDataset(BenchmarkDataset):
DEFAULT_PREFIX_LEN: int
DEFAULT_SUFFIX_LEN: int
DEFAULT_NUM_PREFIXES: int
DEFAULT_OUTPUT_LEN: int
def __init__(self, **kwargs) -> None: ...
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
prefix_len: int = ...,
suffix_len: int = ...,
num_prefixes: int = ...,
output_len: int = ...,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
) -> list[SampleRequest]: ...
class MMStarDataset(HuggingFaceDataset):
DEFAULT_OUTPUT_LEN: int
SUPPORTED_DATASET_PATHS: Incomplete
IS_MULTIMODAL: bool
def sample(
self,
tokenizer: TokenizerLike,
num_requests: int,
output_len: int | None = None,
enable_multimodal_chat: bool = False,
request_id_prefix: str = "",
no_oversample: bool = False,
**kwargs,
) -> list[SampleRequest]: ...
+15
View File
@@ -0,0 +1,15 @@
import argparse
from typing import Any
from vllm.benchmarks.lib.utils import (
convert_to_pytorch_benchmark_format as convert_to_pytorch_benchmark_format,
write_to_json as write_to_json,
)
from vllm.engine.arg_utils import EngineArgs as EngineArgs
from vllm.inputs import PromptType as PromptType
from vllm.sampling_params import BeamSearchParams as BeamSearchParams
def save_to_pytorch_benchmark_format(
args: argparse.Namespace, results: dict[str, Any]
) -> None: ...
def add_cli_args(parser: argparse.ArgumentParser): ...
def main(args: argparse.Namespace): ...
@@ -0,0 +1,113 @@
import aiohttp
from _typeshed import Incomplete
from collections.abc import Awaitable
from dataclasses import dataclass, field
from tqdm.asyncio import tqdm as tqdm
from typing import Literal, Protocol
AIOHTTP_TIMEOUT: Incomplete
class StreamedResponseHandler:
buffer: str
def __init__(self) -> None: ...
def add_chunk(self, chunk_bytes: bytes) -> list[str]: ...
@dataclass
class RequestFuncInput:
prompt: str | list[str]
api_url: str
prompt_len: int
output_len: int
model: str
model_name: str | None = ...
logprobs: int | None = ...
extra_headers: dict | None = ...
extra_body: dict | None = ...
multi_modal_content: dict | list[dict] | None = ...
ignore_eos: bool = ...
language: str | None = ...
request_id: str | None = ...
@dataclass
class RequestFuncOutput:
generated_text: str = ...
success: bool = ...
latency: float = ...
output_tokens: int = ...
ttft: float = ...
itl: list[float] = field(default_factory=list)
tpot: float = ...
prompt_len: int = ...
error: str = ...
start_time: float = ...
input_audio_duration: float = ...
class RequestFunc(Protocol):
def __call__(
self,
request_func_input: RequestFuncInput,
session: aiohttp.ClientSession,
pbar: tqdm | None = None,
) -> Awaitable[RequestFuncOutput]: ...
async def async_request_openai_completions(
request_func_input: RequestFuncInput,
session: aiohttp.ClientSession,
pbar: tqdm | None = None,
) -> RequestFuncOutput: ...
async def async_request_openai_chat_completions(
request_func_input: RequestFuncInput,
session: aiohttp.ClientSession,
pbar: tqdm | None = None,
mm_position: Literal["first", "last"] = "last",
) -> RequestFuncOutput: ...
async def async_request_openai_audio(
request_func_input: RequestFuncInput,
session: aiohttp.ClientSession,
pbar: tqdm | None = None,
) -> RequestFuncOutput: ...
async def async_request_openai_embeddings(
request_func_input: RequestFuncInput,
session: aiohttp.ClientSession,
pbar: tqdm | None = None,
) -> RequestFuncOutput: ...
async def async_request_vllm_rerank(
request_func_input: RequestFuncInput,
session: aiohttp.ClientSession,
pbar: tqdm | None = None,
) -> RequestFuncOutput: ...
async def async_request_openai_embeddings_chat(
request_func_input: RequestFuncInput,
session: aiohttp.ClientSession,
pbar: tqdm | None = None,
mm_position: Literal["first", "last"] = "last",
) -> RequestFuncOutput: ...
async def async_request_openai_embeddings_clip(
request_func_input: RequestFuncInput,
session: aiohttp.ClientSession,
pbar: tqdm | None = None,
) -> RequestFuncOutput: ...
async def async_request_openai_embeddings_vlm2vec(
request_func_input: RequestFuncInput,
session: aiohttp.ClientSession,
pbar: tqdm | None = None,
) -> RequestFuncOutput: ...
async def async_request_infinity_embeddings(
request_func_input: RequestFuncInput,
session: aiohttp.ClientSession,
pbar: tqdm | None = None,
) -> RequestFuncOutput: ...
async def async_request_infinity_embeddings_clip(
request_func_input: RequestFuncInput,
session: aiohttp.ClientSession,
pbar: tqdm | None = None,
) -> RequestFuncOutput: ...
async def async_request_vllm_pooling(
request_func_input: RequestFuncInput,
session: aiohttp.ClientSession,
pbar: tqdm | None = None,
) -> RequestFuncOutput: ...
ASYNC_REQUEST_FUNCS: dict[str, RequestFunc]
POOLING_BACKENDS: Incomplete
OPENAI_COMPATIBLE_BACKENDS: Incomplete
@@ -0,0 +1,18 @@
import aiohttp
from .endpoint_request_func import (
RequestFunc as RequestFunc,
RequestFuncInput as RequestFuncInput,
RequestFuncOutput as RequestFuncOutput,
)
from _typeshed import Incomplete
from vllm.logger import init_logger as init_logger
logger: Incomplete
async def wait_for_endpoint(
request_func: RequestFunc,
test_input: RequestFuncInput,
session: aiohttp.ClientSession,
timeout_seconds: int = 600,
retry_interval: int = 5,
) -> RequestFuncOutput: ...
@@ -0,0 +1,21 @@
import argparse
import json
from collections.abc import Generator
from contextlib import contextmanager
from typing import Any
def extract_field(
args: argparse.Namespace, extra_info: dict[str, Any], field_name: str
) -> str: ...
def use_compile(args: argparse.Namespace, extra_info: dict[str, Any]) -> bool: ...
def convert_to_pytorch_benchmark_format(
args: argparse.Namespace, metrics: dict[str, list], extra_info: dict[str, Any]
) -> list: ...
class InfEncoder(json.JSONEncoder):
def clear_inf(self, o: Any): ...
def iterencode(self, o: Any, *args, **kwargs) -> Any: ...
def write_to_json(filename: str, records: list) -> None: ...
@contextmanager
def default_vllm_config() -> Generator[None]: ...
@@ -0,0 +1,26 @@
import argparse
from typing import Any, Literal
from vllm.benchmarks.datasets import (
MultiModalConversationDataset as MultiModalConversationDataset,
VisionArenaDataset as VisionArenaDataset,
)
from vllm.benchmarks.throughput import get_requests as get_requests
from vllm.engine.arg_utils import EngineArgs as EngineArgs
from vllm.utils.gc_utils import freeze_gc_heap as freeze_gc_heap
from vllm.utils.import_utils import PlaceholderModule as PlaceholderModule
from vllm.v1.engine.llm_engine import LLMEngine as LLMEngine
def get_timing_stats_from_engine(
llm_engine: LLMEngine,
) -> dict[str, dict[str, float]]: ...
def collect_mm_processor_stats(llm_engine: LLMEngine) -> dict[str, list[float]]: ...
def calculate_mm_processor_metrics(
stats_by_stage: dict[str, list[float]],
selected_percentiles: list[float],
*,
unit: Literal["us", "ms", "s"] = "ms",
) -> dict[str, dict[str, float]]: ...
def validate_args(args) -> None: ...
def benchmark_multimodal_processor(args: argparse.Namespace) -> dict[str, Any]: ...
def add_cli_args(parser: argparse.ArgumentParser) -> None: ...
def main(args: argparse.Namespace) -> None: ...
+17
View File
@@ -0,0 +1,17 @@
from pathlib import Path
from typing import Any
from vllm.utils.import_utils import PlaceholderModule as PlaceholderModule
def generate_timeline_plot(
results: list[dict[str, Any]],
output_path: Path,
colors: list[str] | None = None,
itl_thresholds: list[float] | None = None,
labels: list[str] | None = None,
) -> None: ...
def construct_timeline_data(
requests_data: list[dict[str, Any]], itl_thresholds: list[float], labels: list[str]
) -> list[dict[str, Any]]: ...
def generate_dataset_stats_plot(
results: list[dict[str, Any]], output_path: Path
) -> None: ...
+156
View File
@@ -0,0 +1,156 @@
import aiohttp
import argparse
import ssl
from _typeshed import Incomplete
from collections.abc import AsyncGenerator, Iterable
from dataclasses import dataclass
from enum import Enum
from typing import Any, Literal
from vllm.benchmarks.datasets import (
SampleRequest as SampleRequest,
add_dataset_parser as add_dataset_parser,
get_samples as get_samples,
)
from vllm.benchmarks.lib.endpoint_request_func import (
ASYNC_REQUEST_FUNCS as ASYNC_REQUEST_FUNCS,
OPENAI_COMPATIBLE_BACKENDS as OPENAI_COMPATIBLE_BACKENDS,
POOLING_BACKENDS as POOLING_BACKENDS,
RequestFuncInput as RequestFuncInput,
RequestFuncOutput as RequestFuncOutput,
)
from vllm.benchmarks.lib.ready_checker import wait_for_endpoint as wait_for_endpoint
from vllm.benchmarks.lib.utils import (
convert_to_pytorch_benchmark_format as convert_to_pytorch_benchmark_format,
write_to_json as write_to_json,
)
from vllm.tokenizers import (
TokenizerLike as TokenizerLike,
get_tokenizer as get_tokenizer,
)
from vllm.utils.gc_utils import freeze_gc_heap as freeze_gc_heap
from vllm.utils.network_utils import join_host_port as join_host_port
MILLISECONDS_TO_SECONDS_CONVERSION: int
TERM_PLOTLIB_AVAILABLE: Incomplete
async def get_first_model_from_server(
base_url: str,
headers: dict | None = None,
ssl_context: ssl.SSLContext | bool | None = None,
) -> tuple[str, str]: ...
@dataclass
class SpecDecodeMetrics:
num_drafts: int
num_draft_tokens: int
num_accepted_tokens: int
accepted_per_pos: dict[int, int]
async def fetch_spec_decode_metrics(
base_url: str, session: aiohttp.ClientSession
) -> SpecDecodeMetrics | None: ...
class TaskType(Enum):
GENERATION = "generation"
POOLING = "pooling"
@dataclass
class BenchmarkMetrics:
completed: int
failed: int
total_input: int
total_output: int
request_throughput: float
request_goodput: float
output_throughput: float
total_token_throughput: float
mean_ttft_ms: float
median_ttft_ms: float
std_ttft_ms: float
percentiles_ttft_ms: list[tuple[float, float]]
mean_tpot_ms: float
median_tpot_ms: float
std_tpot_ms: float
percentiles_tpot_ms: list[tuple[float, float]]
mean_itl_ms: float
median_itl_ms: float
std_itl_ms: float
percentiles_itl_ms: list[tuple[float, float]]
mean_e2el_ms: float
median_e2el_ms: float
std_e2el_ms: float
percentiles_e2el_ms: list[tuple[float, float]]
max_output_tokens_per_s: float
max_concurrent_requests: int
rtfx: float = ...
@dataclass
class EmbedBenchmarkMetrics:
completed: int
failed: int
total_input: int
request_throughput: float
total_token_throughput: float
mean_e2el_ms: float
std_e2el_ms: float
median_e2el_ms: float
percentiles_e2el_ms: float
async def get_request(
input_requests: list[SampleRequest],
request_rate: float,
burstiness: float = 1.0,
ramp_up_strategy: Literal["linear", "exponential"] | None = None,
ramp_up_start_rps: int | None = None,
ramp_up_end_rps: int | None = None,
) -> AsyncGenerator[tuple[SampleRequest, float], None]: ...
def calculate_metrics_for_embeddings(
outputs: list[RequestFuncOutput], dur_s: float, selected_percentiles: list[float]
) -> EmbedBenchmarkMetrics: ...
def calculate_metrics(
input_requests: list[SampleRequest],
outputs: list[RequestFuncOutput],
dur_s: float,
tokenizer: TokenizerLike,
selected_percentiles: list[float],
goodput_config_dict: dict[str, float],
) -> tuple[BenchmarkMetrics, list[int]]: ...
async def benchmark(
task_type: TaskType,
endpoint_type: str,
api_url: str,
base_url: str,
model_id: str,
model_name: str,
tokenizer: TokenizerLike,
input_requests: list[SampleRequest],
logprobs: int | None,
request_rate: float,
burstiness: float,
disable_tqdm: bool,
num_warmups: int,
profile: bool,
selected_percentile_metrics: list[str],
selected_percentiles: list[float],
ignore_eos: bool,
goodput_config_dict: dict[str, float],
max_concurrency: int | None,
lora_modules: Iterable[str] | None,
extra_headers: dict | None,
extra_body: dict | None,
ramp_up_strategy: Literal["linear", "exponential"] | None = None,
ramp_up_start_rps: int | None = None,
ramp_up_end_rps: int | None = None,
ready_check_timeout_sec: int = 600,
ssl_context: ssl.SSLContext | bool | None = None,
): ...
def check_goodput_args(args): ...
def parse_goodput(slo_pairs): ...
def save_to_pytorch_benchmark_format(
args: argparse.Namespace, results: dict[str, Any], file_name: str
) -> None: ...
def compute_result_filename(
args: argparse.Namespace, model_id: str, label: str, current_dt: str
) -> str | None: ...
def add_cli_args(parser: argparse.ArgumentParser): ...
def main(args: argparse.Namespace) -> dict[str, Any]: ...
async def main_async(args: argparse.Namespace) -> dict[str, Any]: ...
+18
View File
@@ -0,0 +1,18 @@
import argparse
from collections.abc import Generator
from contextlib import contextmanager
from typing import Any
from vllm.benchmarks.lib.utils import (
convert_to_pytorch_benchmark_format as convert_to_pytorch_benchmark_format,
write_to_json as write_to_json,
)
from vllm.engine.arg_utils import EngineArgs as EngineArgs
@contextmanager
def cold_startup() -> Generator[None]: ...
def run_startup_in_subprocess(engine_args, result_queue) -> None: ...
def save_to_pytorch_benchmark_format(
args: argparse.Namespace, results: dict[str, Any]
) -> None: ...
def add_cli_args(parser: argparse.ArgumentParser): ...
def main(args: argparse.Namespace): ...
@@ -0,0 +1,15 @@
import argparse
from .plot import SweepPlotArgs as SweepPlotArgs
from .plot_pareto import SweepPlotParetoArgs as SweepPlotParetoArgs
from .serve import SweepServeArgs as SweepServeArgs
from .serve_workload import SweepServeWorkloadArgs as SweepServeWorkloadArgs
from .startup import SweepStartupArgs as SweepStartupArgs
from _typeshed import Incomplete
from vllm.entrypoints.utils import (
VLLM_SUBCMD_PARSER_EPILOG as VLLM_SUBCMD_PARSER_EPILOG,
)
SUBCOMMANDS: Incomplete
def add_cli_args(parser: argparse.ArgumentParser): ...
def main(args: argparse.Namespace): ...
@@ -0,0 +1,20 @@
import os
from typing import Any
class ParameterSweep(list["ParameterSweepItem"]):
@classmethod
def read_json(cls, filepath: os.PathLike): ...
@classmethod
def read_from_dict(cls, data: dict[str, dict[str, object]]): ...
@classmethod
def from_records(cls, records: list[dict[str, object]]): ...
class ParameterSweepItem(dict[str, object]):
@classmethod
def from_record(cls, record: dict[str, object]): ...
def __or__(self, other: dict[str, Any]): ...
@property
def name(self) -> str: ...
def has_param(self, param_key: str) -> bool: ...
def apply_to_cmd(self, cmd: list[str]) -> list[str]: ...
def as_text(self, sep: str = ", ") -> str: ...
@@ -0,0 +1,137 @@
import abc
import argparse
import pandas as pd
from .utils import sanitize_filename as sanitize_filename
from _typeshed import Incomplete
from abc import ABC, abstractmethod
from dataclasses import dataclass
from pathlib import Path
from types import TracebackType
from typing import ClassVar
from typing_extensions import Self, override
from vllm.utils.collection_utils import full_groupby as full_groupby
from vllm.utils.import_utils import PlaceholderModule as PlaceholderModule
seaborn: Incomplete
@dataclass
class PlotFilterBase(ABC, metaclass=abc.ABCMeta):
var: str
target: str
@classmethod
def parse_str(cls, s: str): ...
@abstractmethod
def apply(self, df: pd.DataFrame) -> pd.DataFrame: ...
@dataclass
class PlotEqualTo(PlotFilterBase):
@override
def apply(self, df: pd.DataFrame) -> pd.DataFrame: ...
@dataclass
class PlotNotEqualTo(PlotFilterBase):
@override
def apply(self, df: pd.DataFrame) -> pd.DataFrame: ...
@dataclass
class PlotLessThan(PlotFilterBase):
@override
def apply(self, df: pd.DataFrame) -> pd.DataFrame: ...
@dataclass
class PlotLessThanOrEqualTo(PlotFilterBase):
@override
def apply(self, df: pd.DataFrame) -> pd.DataFrame: ...
@dataclass
class PlotGreaterThan(PlotFilterBase):
@override
def apply(self, df: pd.DataFrame) -> pd.DataFrame: ...
@dataclass
class PlotGreaterThanOrEqualTo(PlotFilterBase):
@override
def apply(self, df: pd.DataFrame) -> pd.DataFrame: ...
PLOT_FILTERS: dict[str, type[PlotFilterBase]]
class PlotFilters(list[PlotFilterBase]):
@classmethod
def parse_str(cls, s: str): ...
def apply(self, df: pd.DataFrame) -> pd.DataFrame: ...
@dataclass
class PlotBinner:
var: str
bin_size: float
@classmethod
def parse_str(cls, s: str): ...
def apply(self, df: pd.DataFrame) -> pd.DataFrame: ...
PLOT_BINNERS: dict[str, type[PlotBinner]]
class PlotBinners(list[PlotBinner]):
@classmethod
def parse_str(cls, s: str): ...
def apply(self, df: pd.DataFrame) -> pd.DataFrame: ...
class DummyExecutor:
map = map
def __enter__(self) -> Self: ...
def __exit__(
self,
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
exc_traceback: TracebackType | None,
) -> None: ...
def plot(
output_dir: Path,
fig_dir: Path,
fig_by: list[str],
row_by: list[str],
col_by: list[str],
curve_by: list[str],
*,
var_x: str,
var_y: str,
filter_by: PlotFilters,
bin_by: PlotBinners,
scale_x: str | None,
scale_y: str | None,
dry_run: bool,
fig_name: str = "FIGURE",
error_bars: bool = True,
fig_height: float = 6.4,
fig_dpi: int = 300,
): ...
@dataclass
class SweepPlotArgs:
output_dir: Path
fig_dir: Path
fig_by: list[str]
row_by: list[str]
col_by: list[str]
curve_by: list[str]
var_x: str
var_y: str
filter_by: PlotFilters
bin_by: PlotBinners
scale_x: str | None
scale_y: str | None
dry_run: bool
fig_name: str = ...
error_bars: bool = ...
fig_height: float = ...
fig_dpi: int = ...
parser_name: ClassVar[str] = ...
parser_help: ClassVar[str] = ...
@classmethod
def from_cli_args(cls, args: argparse.Namespace): ...
@classmethod
def add_cli_args(
cls, parser: argparse.ArgumentParser
) -> argparse.ArgumentParser: ...
def run_main(args: SweepPlotArgs): ...
def main(args: argparse.Namespace): ...
@@ -0,0 +1,36 @@
import argparse
from .plot import DummyExecutor as DummyExecutor
from .utils import sanitize_filename as sanitize_filename
from _typeshed import Incomplete
from dataclasses import dataclass
from pathlib import Path
from typing import ClassVar
from vllm.utils.collection_utils import full_groupby as full_groupby
from vllm.utils.import_utils import PlaceholderModule as PlaceholderModule
seaborn: Incomplete
def plot_pareto(
output_dir: Path,
user_count_var: str | None,
gpu_count_var: str | None,
label_by: list[str],
*,
dry_run: bool,
): ...
@dataclass
class SweepPlotParetoArgs:
output_dir: Path
user_count_var: str | None
gpu_count_var: str | None
label_by: list[str]
dry_run: bool
parser_name: ClassVar[str] = ...
parser_help: ClassVar[str] = ...
@classmethod
def from_cli_args(cls, args: argparse.Namespace): ...
@classmethod
def add_cli_args(cls, parser: argparse.ArgumentParser): ...
def run_main(args: SweepPlotParetoArgs): ...
def main(args: argparse.Namespace): ...
@@ -0,0 +1,101 @@
import argparse
import contextlib
from .param_sweep import (
ParameterSweep as ParameterSweep,
ParameterSweepItem as ParameterSweepItem,
)
from .server import ServerProcess as ServerProcess
from .utils import sanitize_filename as sanitize_filename
from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import ClassVar
from vllm.utils.import_utils import PlaceholderModule as PlaceholderModule
@contextlib.contextmanager
def run_server(
serve_cmd: list[str],
after_bench_cmd: list[str],
*,
show_stdout: bool,
serve_overrides: ParameterSweepItem,
dry_run: bool,
server_ready_timeout: int = 300,
): ...
def run_benchmark(
server: ServerProcess | None,
bench_cmd: list[str],
*,
serve_overrides: ParameterSweepItem,
bench_overrides: ParameterSweepItem,
run_number: int,
output_path: Path,
dry_run: bool,
): ...
def server_ctx(
serve_cmd: list[str],
after_bench_cmd: list[str],
*,
show_stdout: bool,
serve_comb: ParameterSweepItem,
bench_params: ParameterSweep,
experiment_dir: Path,
dry_run: bool,
server_ready_timeout: int = 300,
): ...
def run_comb(
server: ServerProcess | None,
bench_cmd: list[str],
*,
serve_comb: ParameterSweepItem,
bench_comb: ParameterSweepItem,
link_vars: list[tuple[str, str]],
base_path: Path,
num_runs: int,
dry_run: bool,
): ...
def run_combs(
serve_cmd: list[str],
bench_cmd: list[str],
after_bench_cmd: list[str],
*,
show_stdout: bool,
server_ready_timeout: int,
serve_params: ParameterSweep,
bench_params: ParameterSweep,
link_vars: list[tuple[str, str]],
experiment_dir: Path,
num_runs: int,
dry_run: bool,
): ...
@dataclass
class SweepServeArgs:
serve_cmd: list[str]
bench_cmd: list[str]
after_bench_cmd: list[str]
show_stdout: bool
server_ready_timeout: int
serve_params: ParameterSweep
bench_params: ParameterSweep
link_vars: list[tuple[str, str]]
output_dir: Path
experiment_name: str
num_runs: int
dry_run: bool
resume: bool
parser_name: ClassVar[str] = ...
parser_help: ClassVar[str] = ...
@classmethod
def from_cli_args(cls, args: argparse.Namespace): ...
@classmethod
def add_cli_args(
cls, parser: argparse.ArgumentParser
) -> argparse.ArgumentParser: ...
@staticmethod
def parse_link_vars(s: str) -> list[tuple[str, str]]: ...
def resolve_experiment_dir(self) -> Path: ...
@contextmanager
def run_ctx(self, experiment_dir: Path): ...
def run_main(args: SweepServeArgs): ...
def main(args: argparse.Namespace): ...
@@ -0,0 +1,77 @@
import argparse
from .param_sweep import (
ParameterSweep as ParameterSweep,
ParameterSweepItem as ParameterSweepItem,
)
from .serve import (
SweepServeArgs as SweepServeArgs,
run_comb as run_comb,
server_ctx as server_ctx,
)
from .server import ServerProcess as ServerProcess
from _typeshed import Incomplete
from dataclasses import dataclass
from pathlib import Path
from typing import ClassVar
from vllm.benchmarks.datasets import DEFAULT_NUM_PROMPTS as DEFAULT_NUM_PROMPTS
from vllm.utils.import_utils import PlaceholderModule as PlaceholderModule
WorkloadVariable: Incomplete
def run_comb_workload(
server: ServerProcess | None,
bench_cmd: list[str],
*,
serve_comb: ParameterSweepItem,
bench_comb: ParameterSweepItem,
link_vars: list[tuple[str, str]],
experiment_dir: Path,
num_runs: int,
dry_run: bool,
workload_var: WorkloadVariable,
workload_value: int,
) -> list[dict[str, object]] | None: ...
def explore_comb_workloads(
server: ServerProcess | None,
bench_cmd: list[str],
*,
serve_comb: ParameterSweepItem,
bench_comb: ParameterSweepItem,
link_vars: list[tuple[str, str]],
workload_var: WorkloadVariable,
workload_iters: int,
experiment_dir: Path,
num_runs: int,
dry_run: bool,
): ...
def explore_combs_workloads(
serve_cmd: list[str],
bench_cmd: list[str],
after_bench_cmd: list[str],
*,
show_stdout: bool,
server_ready_timeout: int,
serve_params: ParameterSweep,
bench_params: ParameterSweep,
link_vars: list[tuple[str, str]],
workload_var: WorkloadVariable,
workload_iters: int,
experiment_dir: Path,
num_runs: int,
dry_run: bool,
): ...
@dataclass
class SweepServeWorkloadArgs(SweepServeArgs):
workload_var: WorkloadVariable
workload_iters: int
parser_name: ClassVar[str] = ...
parser_help: ClassVar[str] = ...
@classmethod
def from_cli_args(cls, args: argparse.Namespace): ...
@classmethod
def add_cli_args(
cls, parser: argparse.ArgumentParser
) -> argparse.ArgumentParser: ...
def run_main(args: SweepServeWorkloadArgs): ...
def main(args: argparse.Namespace): ...
@@ -0,0 +1,26 @@
from _typeshed import Incomplete
from types import TracebackType
from typing_extensions import Self
class ServerProcess:
VLLM_RESET_CACHE_ENDPOINTS: Incomplete
server_cmd: Incomplete
after_bench_cmd: Incomplete
show_stdout: Incomplete
def __init__(
self, server_cmd: list[str], after_bench_cmd: list[str], *, show_stdout: bool
) -> None: ...
def __enter__(self) -> Self: ...
def __exit__(
self,
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
exc_traceback: TracebackType | None,
) -> None: ...
def start(self) -> None: ...
def stop(self) -> None: ...
def run_subcommand(self, cmd: list[str]): ...
def after_bench(self) -> None: ...
def is_server_ready(self) -> bool: ...
def wait_until_ready(self, timeout: int) -> None: ...
def reset_caches(self) -> None: ...
@@ -0,0 +1,69 @@
import argparse
import pandas as pd
from .param_sweep import (
ParameterSweep as ParameterSweep,
ParameterSweepItem as ParameterSweepItem,
)
from .utils import sanitize_filename as sanitize_filename
from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import ClassVar
from vllm.utils.argparse_utils import FlexibleArgumentParser as FlexibleArgumentParser
from vllm.utils.import_utils import PlaceholderModule as PlaceholderModule
def run_benchmark(
startup_cmd: list[str],
*,
serve_overrides: ParameterSweepItem,
startup_overrides: ParameterSweepItem,
run_number: int,
output_path: Path,
show_stdout: bool,
dry_run: bool,
) -> dict[str, object] | None: ...
def run_comb(
startup_cmd: list[str],
*,
serve_comb: ParameterSweepItem,
startup_comb: ParameterSweepItem,
base_path: Path,
num_runs: int,
show_stdout: bool,
dry_run: bool,
) -> list[dict[str, object]] | None: ...
def run_combs(
startup_cmd: list[str],
*,
serve_params: ParameterSweep,
startup_params: ParameterSweep,
experiment_dir: Path,
num_runs: int,
show_stdout: bool,
dry_run: bool,
) -> pd.DataFrame | None: ...
@dataclass
class SweepStartupArgs:
startup_cmd: list[str]
serve_params: ParameterSweep
startup_params: ParameterSweep
output_dir: Path
experiment_name: str
num_runs: int
show_stdout: bool
dry_run: bool
resume: bool
parser_name: ClassVar[str] = ...
parser_help: ClassVar[str] = ...
@classmethod
def from_cli_args(cls, args: argparse.Namespace): ...
@classmethod
def add_cli_args(
cls, parser: argparse.ArgumentParser
) -> argparse.ArgumentParser: ...
def resolve_experiment_dir(self) -> Path: ...
@contextmanager
def run_ctx(self, experiment_dir: Path): ...
def run_main(args: SweepStartupArgs): ...
def main(args: argparse.Namespace): ...
@@ -0,0 +1 @@
def sanitize_filename(filename: str) -> str: ...
@@ -0,0 +1,80 @@
import argparse
import torch
from typing import Any
from vllm.benchmarks.datasets import (
AIMODataset as AIMODataset,
BurstGPTDataset as BurstGPTDataset,
ConversationDataset as ConversationDataset,
InstructCoderDataset as InstructCoderDataset,
MultiModalConversationDataset as MultiModalConversationDataset,
PrefixRepetitionRandomDataset as PrefixRepetitionRandomDataset,
RandomDataset as RandomDataset,
RandomDatasetForReranking as RandomDatasetForReranking,
RandomMultiModalDataset as RandomMultiModalDataset,
SampleRequest as SampleRequest,
ShareGPTDataset as ShareGPTDataset,
SonnetDataset as SonnetDataset,
VisionArenaDataset as VisionArenaDataset,
add_random_dataset_base_args as add_random_dataset_base_args,
add_random_multimodal_dataset_args as add_random_multimodal_dataset_args,
)
from vllm.benchmarks.lib.utils import (
convert_to_pytorch_benchmark_format as convert_to_pytorch_benchmark_format,
write_to_json as write_to_json,
)
from vllm.engine.arg_utils import (
AsyncEngineArgs as AsyncEngineArgs,
EngineArgs as EngineArgs,
)
from vllm.inputs import TextPrompt as TextPrompt, TokensPrompt as TokensPrompt
from vllm.lora.request import LoRARequest as LoRARequest
from vllm.outputs import RequestOutput as RequestOutput
from vllm.platforms import current_platform as current_platform
from vllm.sampling_params import BeamSearchParams as BeamSearchParams
from vllm.tokenizers import (
TokenizerLike as TokenizerLike,
get_tokenizer as get_tokenizer,
)
from vllm.utils.async_utils import merge_async_iterators as merge_async_iterators
def run_vllm(
requests: list[SampleRequest],
n: int,
engine_args: EngineArgs,
do_profile: bool,
disable_detokenize: bool = False,
) -> tuple[float, list[RequestOutput] | None]: ...
def run_vllm_chat(
requests: list[SampleRequest],
n: int,
engine_args: EngineArgs,
do_profile: bool,
disable_detokenize: bool = False,
) -> tuple[float, list[RequestOutput]]: ...
async def run_vllm_async(
requests: list[SampleRequest],
n: int,
engine_args: AsyncEngineArgs,
do_profile: bool,
disable_frontend_multiprocessing: bool = False,
disable_detokenize: bool = False,
) -> float: ...
def run_hf(
requests: list[SampleRequest],
model: str,
tokenizer: TokenizerLike,
n: int,
max_batch_size: int,
trust_remote_code: bool,
disable_detokenize: bool = False,
dtype: torch.dtype | None = ...,
enable_torch_compile: bool = False,
) -> float: ...
def save_to_pytorch_benchmark_format(
args: argparse.Namespace, results: dict[str, Any]
) -> None: ...
def get_requests(args, tokenizer): ...
def filter_requests_for_dp(requests, data_parallel_size): ...
def validate_args(args) -> None: ...
def add_cli_args(parser: argparse.ArgumentParser): ...
def main(args: argparse.Namespace): ...
+79
View File
@@ -0,0 +1,79 @@
from _typeshed import Incomplete
from typing import NamedTuple
from vllm.envs import environment_variables as environment_variables
TORCH_AVAILABLE: bool
class SystemEnv(NamedTuple):
torch_version: Incomplete
is_debug_build: Incomplete
cuda_compiled_version: Incomplete
gcc_version: Incomplete
clang_version: Incomplete
cmake_version: Incomplete
os: Incomplete
libc_version: Incomplete
python_version: Incomplete
python_platform: Incomplete
is_cuda_available: Incomplete
cuda_runtime_version: Incomplete
cuda_module_loading: Incomplete
nvidia_driver_version: Incomplete
nvidia_gpu_models: Incomplete
cudnn_version: Incomplete
pip_version: Incomplete
pip_packages: Incomplete
conda_packages: Incomplete
hip_compiled_version: Incomplete
hip_runtime_version: Incomplete
miopen_runtime_version: Incomplete
caching_allocator_config: Incomplete
is_xnnpack_available: Incomplete
cpu_info: Incomplete
rocm_version: Incomplete
vllm_version: Incomplete
vllm_build_flags: Incomplete
gpu_topo: Incomplete
env_vars: Incomplete
DEFAULT_CONDA_PATTERNS: Incomplete
DEFAULT_PIP_PATTERNS: Incomplete
def run(command): ...
def run_and_read_all(run_lambda, command): ...
def run_and_parse_first_match(run_lambda, command, regex): ...
def get_conda_packages(run_lambda, patterns=None): ...
def get_gcc_version(run_lambda): ...
def get_clang_version(run_lambda): ...
def get_cmake_version(run_lambda): ...
def get_nvidia_driver_version(run_lambda): ...
def get_gpu_info(run_lambda): ...
def get_running_cuda_version(run_lambda): ...
def get_cudnn_version(run_lambda): ...
def get_nvidia_smi(): ...
def get_rocm_version(run_lambda): ...
def get_vllm_version(): ...
def summarize_vllm_build_flags(): ...
def get_gpu_topo(run_lambda): ...
def get_cpu_info(run_lambda): ...
def get_platform(): ...
def get_mac_version(run_lambda): ...
def get_windows_version(run_lambda): ...
def get_lsb_version(run_lambda): ...
def check_release_file(run_lambda): ...
def get_os(run_lambda): ...
def get_python_platform(): ...
def get_libc_version(): ...
def is_uv_venv(): ...
def get_pip_packages(run_lambda, patterns=None): ...
def get_cachingallocator_config(): ...
def get_cuda_module_loading_config(): ...
def is_xnnpack_available(): ...
def get_env_vars(): ...
def get_env_info(): ...
env_info_fmt: Incomplete
def pretty_str(envinfo): ...
def get_pretty_env_info(): ...
def main() -> None: ...
+158
View File
@@ -0,0 +1,158 @@
import dataclasses
import torch
import torch.fx as fx
from .compiler_interface import (
CompilerInterface as CompilerInterface,
EagerAdaptor as EagerAdaptor,
InductorAdaptor as InductorAdaptor,
InductorStandaloneAdaptor as InductorStandaloneAdaptor,
is_compile_cache_enabled as is_compile_cache_enabled,
)
from .counter import compilation_counter as compilation_counter
from .partition_rules import (
inductor_partition_rule_context as inductor_partition_rule_context,
should_split as should_split,
)
from .passes.inductor_pass import (
InductorPass as InductorPass,
pass_context as pass_context,
)
from .passes.pass_manager import PostGradPassManager as PostGradPassManager
from _typeshed import Incomplete
from collections.abc import Callable as Callable, Generator, Sequence
from contextlib import contextmanager
from typing import Any
from vllm.config import (
CUDAGraphMode as CUDAGraphMode,
CompilationConfig as CompilationConfig,
VllmConfig as VllmConfig,
)
from vllm.config.compilation import DynamicShapesType as DynamicShapesType
from vllm.config.utils import Range as Range, hash_factors as hash_factors
from vllm.logger import init_logger as init_logger
from vllm.logging_utils import lazy as lazy
from vllm.platforms import current_platform as current_platform
from vllm.tracing import (
instrument as instrument,
instrument_manual as instrument_manual,
)
from vllm.utils.import_utils import resolve_obj_by_qualname as resolve_obj_by_qualname
logger: Incomplete
def make_copy_and_call(
sym_tensor_indices: list[int],
input_buffers: list[torch.Tensor | None],
callable_fn: Callable[..., Any],
) -> Callable[..., Any]: ...
def make_compiler(compilation_config: CompilationConfig) -> CompilerInterface: ...
class CompilerManager:
cache: dict[tuple[Range, int, str], Any]
is_cache_updated: bool
compilation_config: Incomplete
compiler: Incomplete
loaded_artifacts: dict[str, Any]
def __init__(self, compilation_config: CompilationConfig) -> None: ...
def compute_hash(self, vllm_config: VllmConfig) -> str: ...
@contextmanager
def compile_context(self, compile_range: Range) -> Generator[None, None, None]: ...
disable_cache: Incomplete
cache_dir: Incomplete
cache_file_path: Incomplete
def initialize_cache(
self, cache_dir: str, disable_cache: bool = False, prefix: str = ""
) -> None: ...
def save_to_file(self) -> None: ...
def load(
self,
graph: fx.GraphModule,
example_inputs: list[Any],
graph_index: int,
compile_range: Range,
) -> Callable[..., Any] | None: ...
def compile(
self,
graph: fx.GraphModule,
example_inputs: list[Any],
additional_inductor_config: dict[str, Any],
compilation_config: CompilationConfig,
compile_range: Range,
graph_index: int = 0,
num_graphs: int = 1,
) -> Any: ...
class StopCompiling(BaseException): ...
@dataclasses.dataclass
class SplitItem:
submod_name: str
graph_id: int
is_splitting_graph: bool
graph: fx.GraphModule
def split_graph(
graph: fx.GraphModule, splitting_ops: list[str]
) -> tuple[fx.GraphModule, list[SplitItem]]: ...
compilation_start_time: float
def wrap_with_cudagraph_if_needed(
piecewise_backend: Any,
vllm_config: VllmConfig,
compilation_config: CompilationConfig,
is_first_graph: bool,
is_last_graph: bool,
) -> Any: ...
class PiecewiseCompileInterpreter(torch.fx.Interpreter):
compile_submod_names: Incomplete
compilation_config: Incomplete
vllm_config: Incomplete
vllm_backend: Incomplete
extra_traceback: bool
def __init__(
self,
module: torch.fx.GraphModule,
compile_submod_names: list[str],
vllm_config: VllmConfig,
vllm_backend: VllmBackend,
) -> None: ...
def run(self, *args: Any) -> Any: ...
def call_module(
self,
target: torch.fx.node.Target,
args: tuple[torch.fx.node.Argument, ...],
kwargs: dict[str, Any],
) -> Any: ...
model_tag: str
model_is_encoder: bool
@contextmanager
def set_model_tag(
tag: str, is_encoder: bool = False
) -> Generator[None, None, None]: ...
class VllmBackend:
vllm_config: VllmConfig
compilation_config: CompilationConfig
graph: fx.GraphModule
split_gm: fx.GraphModule
piecewise_graphs: list[SplitItem]
returned_callable: Callable[..., Any]
post_grad_passes: Sequence[Callable[..., Any]]
compiler_manager: CompilerManager
inductor_config: dict[str, Any]
prefix: Incomplete
is_encoder: Incomplete
pass_manager: Incomplete
pass_key: Incomplete
def __init__(
self, vllm_config: VllmConfig, prefix: str = "", is_encoder: bool = False
) -> None: ...
def collect_standalone_compile_artifacts(
self,
) -> tuple[Any, dict[str, list[int]] | None, dict[str, bool] | None]: ...
def configure_post_pass(self) -> None: ...
def __call__(self, graph: fx.GraphModule, example_inputs: Sequence[Any]) -> Any: ...
@@ -0,0 +1,13 @@
from collections.abc import Callable as Callable
from typing import Any, Protocol
from vllm.config import CUDAGraphMode as CUDAGraphMode, VllmConfig as VllmConfig
class AbstractStaticGraphWrapper(Protocol):
def __init__(
self,
runnable: Callable[..., Any],
vllm_config: VllmConfig,
runtime_mode: CUDAGraphMode,
**kwargs: Any,
) -> None: ...
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
@@ -0,0 +1,77 @@
import contextlib
import torch
from _typeshed import Incomplete
from collections.abc import Callable as Callable, Generator, Sequence
from torch._dynamo.aot_compile import SerializableCallable
from typing import Any, Literal
from vllm.compilation.compiler_interface import (
get_inductor_factors as get_inductor_factors,
)
from vllm.config import (
VllmConfig as VllmConfig,
get_current_vllm_config as get_current_vllm_config,
)
from vllm.config.utils import hash_factors as hash_factors
from vllm.logger import init_logger as init_logger
from vllm.utils.hashing import safe_hash as safe_hash
SerializableCallable = object
logger: Incomplete
class StandaloneCompiledArtifacts:
submodule_bytes: dict[str, str]
submodule_bytes_store: dict[str, bytes]
loaded_submodule_store: dict[str, Any]
def __init__(self) -> None: ...
def insert(self, submod_name: str, shape: str, entry: bytes) -> None: ...
def get(self, submod_name: str, shape: str) -> bytes: ...
def get_loaded(self, submod_name: str, shape: str) -> Any: ...
def size_bytes(self) -> int: ...
def num_artifacts(self) -> int: ...
def num_entries(self) -> int: ...
def submodule_names(self) -> list[str]: ...
def load_all(self) -> None: ...
@contextlib.contextmanager
def patch_pytree_map_over_slice() -> Generator[None, None, Incomplete]: ...
class VllmSerializableFunction(SerializableCallable):
graph_module: Incomplete
example_inputs: Incomplete
prefix: Incomplete
optimized_call: Incomplete
is_encoder: Incomplete
shape_env: Incomplete
vllm_backend: Incomplete
sym_tensor_indices: Incomplete
aot_autograd_config: Incomplete
def __init__(
self,
graph_module: torch.fx.GraphModule,
example_inputs: Sequence[Any],
prefix: str,
optimized_call: Callable[..., Any],
is_encoder: bool = False,
vllm_backend: Any | None = None,
sym_tensor_indices: list[int] | None = None,
aot_autograd_config: dict[str, Any] | None = None,
) -> None: ...
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
@classmethod
def serialize_compile_artifacts(
cls, compiled_fn: VllmSerializableFunction
) -> bytes: ...
@classmethod
def deserialize_compile_artifacts(cls, data: bytes) -> VllmSerializableFunction: ...
def finalize_loading(self, vllm_config: VllmConfig) -> None: ...
@property
def co_name(self) -> Literal["VllmSerializableFunction"]: ...
def reconstruct_serializable_fn_from_mega_artifact(
state: dict[str, Any],
standalone_compile_artifacts: StandaloneCompiledArtifacts,
vllm_config: VllmConfig,
sym_shape_indices_map: dict[str, list[int]],
returns_tuple_map: dict[str, bool],
) -> VllmSerializableFunction: ...
def aot_compile_hash_factors(vllm_config: VllmConfig) -> list[str]: ...
@@ -0,0 +1,117 @@
import contextlib
import torch.fx as fx
from _typeshed import Incomplete
from collections.abc import Callable as Callable
from typing import Any, Literal
from vllm.compilation.counter import compilation_counter as compilation_counter
from vllm.config import VllmConfig as VllmConfig
from vllm.config.utils import Range as Range
from vllm.logger import init_logger as init_logger
from vllm.utils.hashing import safe_hash as safe_hash
from vllm.utils.torch_utils import is_torch_equal_or_newer as is_torch_equal_or_newer
logger: Incomplete
class CompilerInterface:
name: str
def initialize_cache(
self, cache_dir: str, disable_cache: bool = False, prefix: str = ""
) -> None: ...
def compute_hash(self, vllm_config: VllmConfig) -> str: ...
def compile(
self,
graph: fx.GraphModule,
example_inputs: list[Any],
compiler_config: dict[str, Any],
compile_range: Range,
key: str | None = None,
) -> tuple[Callable[..., Any] | None, Any | None]: ...
def load(
self,
handle: Any,
graph: fx.GraphModule,
example_inputs: list[Any],
graph_index: int,
compile_range: Range,
) -> Callable[..., Any]: ...
class AlwaysHitShapeEnv:
guards: list[Any]
def __init__(self) -> None: ...
def evaluate_guards_expression(
self, *args: Any, **kwargs: Any
) -> Literal[True]: ...
def get_pruned_guards(self, *args: Any, **kwargs: Any) -> list[Any]: ...
def produce_guards_expression(self, *args: Any, **kwargs: Any) -> Literal[""]: ...
def get_inductor_factors() -> list[Any]: ...
def is_compile_cache_enabled(
vllm_additional_inductor_config: dict[str, Any],
) -> bool: ...
class InductorStandaloneAdaptor(CompilerInterface):
name: str
save_format: Incomplete
def __init__(self, save_format: Literal["binary", "unpacked"]) -> None: ...
def compute_hash(self, vllm_config: VllmConfig) -> str: ...
cache_dir: Incomplete
def initialize_cache(
self, cache_dir: str, disable_cache: bool = False, prefix: str = ""
) -> None: ...
def compile(
self,
graph: fx.GraphModule,
example_inputs: list[Any],
compiler_config: dict[str, Any],
compile_range: Range,
key: str | None = None,
) -> tuple[Callable[..., Any] | None, Any | None]: ...
def load(
self,
handle: Any,
graph: fx.GraphModule,
example_inputs: list[Any],
graph_index: int,
compile_range: Range,
) -> Callable[..., Any]: ...
class InductorAdaptor(CompilerInterface):
name: str
def compute_hash(self, vllm_config: VllmConfig) -> str: ...
cache_dir: Incomplete
prefix: Incomplete
base_cache_dir: Incomplete
def initialize_cache(
self, cache_dir: str, disable_cache: bool = False, prefix: str = ""
) -> None: ...
def compile(
self,
graph: fx.GraphModule,
example_inputs: list[Any],
compiler_config: dict[str, Any],
compile_range: Range,
key: str | None = None,
) -> tuple[Callable[..., Any] | None, Any | None]: ...
def load(
self,
handle: Any,
graph: fx.GraphModule,
example_inputs: list[Any],
graph_index: int,
compile_range: Range,
) -> Callable[..., Any]: ...
def metrics_context(self) -> contextlib.AbstractContextManager[Any]: ...
def set_inductor_config(config: dict[str, Any], compile_range: Range) -> None: ...
def set_functorch_config() -> None: ...
class EagerAdaptor(CompilerInterface):
name: str
def compile(
self,
graph: fx.GraphModule,
example_inputs: list[Any],
compiler_config: dict[str, Any],
compile_range: Range,
key: str | None = None,
) -> tuple[Callable[..., Any] | None, Any | None]: ...
@@ -0,0 +1,26 @@
import dataclasses
from _typeshed import Incomplete
from collections.abc import Generator
from contextlib import contextmanager
from typing import Any
@dataclasses.dataclass
class CompilationCounter:
num_models_seen: int = ...
num_graphs_seen: int = ...
num_piecewise_graphs_seen: int = ...
num_piecewise_capturable_graphs_seen: int = ...
num_backend_compilations: int = ...
num_gpu_runner_capture_triggers: int = ...
num_cudagraph_captured: int = ...
num_inductor_compiles: int = ...
num_eager_compiles: int = ...
num_cache_entries_updated: int = ...
num_compiled_artifacts_saved: int = ...
num_compiled_artifacts_loaded: int = ...
stock_torch_compile_count: int = ...
def clone(self) -> CompilationCounter: ...
@contextmanager
def expect(self, **kwargs: Any) -> Generator[None, None, None]: ...
compilation_counter: Incomplete
@@ -0,0 +1,86 @@
import dataclasses
import torch
from _typeshed import Incomplete
from collections.abc import Callable as Callable
from typing import Any
from vllm.compilation.counter import compilation_counter as compilation_counter
from vllm.compilation.monitor import (
validate_cudagraph_capturing_enabled as validate_cudagraph_capturing_enabled,
)
from vllm.config import CUDAGraphMode as CUDAGraphMode, VllmConfig as VllmConfig
from vllm.distributed.device_communicators.pynccl_allocator import (
set_graph_pool_id as set_graph_pool_id,
)
from vllm.forward_context import (
BatchDescriptor as BatchDescriptor,
get_forward_context as get_forward_context,
)
from vllm.logger import init_logger as init_logger
from vllm.model_executor.offloader.base import get_offloader as get_offloader
from vllm.platforms import current_platform as current_platform
from vllm.utils.torch_utils import (
current_stream as current_stream,
weak_ref_tensors as weak_ref_tensors,
)
logger: Incomplete
@dataclasses.dataclass(frozen=True)
class CUDAGraphStat:
num_unpadded_tokens: int
num_padded_tokens: int
num_paddings: int
runtime_mode: str
class CUDAGraphLogging:
COLUMN_HEADERS: Incomplete
cg_mode: Incomplete
cg_capture_sizes: Incomplete
settings_header: Incomplete
def __init__(
self, cg_mode: CUDAGraphMode, cg_capture_sizes: list[int] | None
) -> None: ...
stats: list[CUDAGraphStat]
def reset(self) -> None: ...
def observe(self, cudagraph_stat: CUDAGraphStat) -> None: ...
def generate_metric_table(self) -> str: ...
def log(self, log_fn: Callable[..., Any] = ...) -> None: ...
@dataclasses.dataclass
class CUDAGraphEntry:
batch_descriptor: BatchDescriptor
cudagraph: torch.cuda.CUDAGraph | None = ...
output: Any | None = ...
input_addresses: list[int] | None = ...
@dataclasses.dataclass
class CUDAGraphOptions:
debug_log_enable: bool = ...
gc_disable: bool = ...
weak_ref_output: bool = ...
class CUDAGraphWrapper:
@classmethod
def clear_all_graphs(cls) -> None: ...
runnable: Incomplete
vllm_config: Incomplete
runtime_mode: Incomplete
compilation_config: Incomplete
first_run_finished: bool
is_debugging_mode: Incomplete
graph_pool: Incomplete
cudagraph_options: Incomplete
concrete_cudagraph_entries: dict[BatchDescriptor, CUDAGraphEntry]
def __init__(
self,
runnable: Callable[..., Any],
vllm_config: VllmConfig,
runtime_mode: CUDAGraphMode,
cudagraph_options: CUDAGraphOptions | None = None,
) -> None: ...
def __getattr__(self, key: str) -> Any: ...
def unwrap(self) -> Callable[..., Any]: ...
@property
def cudagraph_wrapper(self) -> CUDAGraphWrapper: ...
def clear_graphs(self) -> None: ...
def __call__(self, *args: Any, **kwargs: Any) -> Any | None: ...
@@ -0,0 +1,58 @@
import contextlib
from .monitor import (
monitor_profiling_run as monitor_profiling_run,
monitor_torch_compile as monitor_torch_compile,
)
from _typeshed import Incomplete
from collections.abc import Callable as Callable, Generator
from typing import Any, overload
from vllm.compilation.counter import compilation_counter as compilation_counter
from vllm.compilation.wrapper import (
TorchCompileWithNoGuardsWrapper as TorchCompileWithNoGuardsWrapper,
)
from vllm.config import (
CompilationMode as CompilationMode,
VllmConfig as VllmConfig,
get_current_vllm_config as get_current_vllm_config,
set_current_vllm_config as set_current_vllm_config,
)
from vllm.config.compilation import DynamicShapesType as DynamicShapesType
from vllm.forward_context import (
get_forward_context as get_forward_context,
is_forward_context_available as is_forward_context_available,
)
from vllm.logger import init_logger as init_logger
from vllm.sequence import IntermediateTensors as IntermediateTensors
from vllm.utils.import_utils import resolve_obj_by_qualname as resolve_obj_by_qualname
from vllm.utils.torch_utils import is_torch_equal_or_newer as is_torch_equal_or_newer
SourceInfo = Any
logger: Incomplete
IGNORE_COMPILE_KEY: str
def should_torch_compile_mm_encoder(vllm_config: VllmConfig) -> bool: ...
def ignore_torch_compile(cls) -> type[_T]: ...
@overload
def support_torch_compile(
*, enable_if: Callable[[VllmConfig], bool] | None = None
) -> Callable[[type[_T]], type[_T]]: ...
@overload
def support_torch_compile(
*, dynamic_arg_dims: dict[str, int | list[int]] | None
) -> Callable[[type[_T]], type[_T]]: ...
@overload
def support_torch_compile(
*, mark_unbacked_dims: dict[str, int | list[int]] | None
) -> Callable[[type[_T]], type[_T]]: ...
@overload
def support_torch_compile(
*,
dynamic_arg_dims: dict[str, int | list[int]] | None,
mark_unbacked_dims: dict[str, int | list[int]] | None,
) -> Callable[[type[_T]], type[_T]]: ...
@overload
def support_torch_compile(cls) -> type[_T]: ...
@contextlib.contextmanager
def maybe_use_cudagraph_partition_wrapper(
vllm_config: VllmConfig,
) -> Generator[None, None, None]: ...
@@ -0,0 +1,20 @@
import contextlib
from _typeshed import Incomplete
from collections.abc import Generator
from vllm.config import CompilationMode as CompilationMode, VllmConfig as VllmConfig
from vllm.logger import init_logger as init_logger
logger: Incomplete
torch_compile_start_time: float
@contextlib.contextmanager
def monitor_torch_compile(
vllm_config: VllmConfig, message: str = "torch.compile took %.2f s in total"
) -> Generator[None, None, None]: ...
@contextlib.contextmanager
def monitor_profiling_run() -> Generator[None, None, None]: ...
cudagraph_capturing_enabled: bool
def validate_cudagraph_capturing_enabled() -> None: ...
def set_cudagraph_capturing_enabled(enabled: bool) -> None: ...
@@ -0,0 +1,13 @@
import contextlib
import torch
from _typeshed import Incomplete
from collections.abc import Generator
from vllm.logger import init_logger as init_logger
logger: Incomplete
def should_split(node: torch.fx.Node, splitting_ops: list[str]) -> bool: ...
@contextlib.contextmanager
def inductor_partition_rule_context(
splitting_ops: list[str] | None,
) -> Generator[None, None, None]: ...
@@ -0,0 +1,68 @@
import abc
import torch
from ..inductor_pass import enable_fake_mode as enable_fake_mode
from ..vllm_inductor_pass import (
VllmInductorPass as VllmInductorPass,
VllmPatternMatcherPass as VllmPatternMatcherPass,
)
from .matcher_utils import (
MatcherQuantFP8 as MatcherQuantFP8,
MatcherSiluAndMul as MatcherSiluAndMul,
)
from .rms_quant_fusion import (
QUANT_OPS as QUANT_OPS,
empty_bf16 as empty_bf16,
empty_fp32 as empty_fp32,
empty_i32 as empty_i32,
)
from _typeshed import Incomplete
from abc import ABC, abstractmethod
from torch._inductor.pattern_matcher import PatternMatcherPass
from torch._ops import OpOverload as OpOverload
from typing import Any
from vllm.config import VllmConfig as VllmConfig
from vllm.logger import init_logger as init_logger
from vllm.model_executor.layers.quantization.utils.quant_utils import (
QuantKey as QuantKey,
kFp8StaticTensorSym as kFp8StaticTensorSym,
kNvfp4Dynamic as kNvfp4Dynamic,
)
from vllm.platforms import current_platform as current_platform
logger: Incomplete
FP8_DTYPE: Incomplete
FP4_DTYPE: Incomplete
SILU_MUL_OP: Incomplete
FUSED_OPS: dict[QuantKey, OpOverload]
silu_and_mul_nvfp4_quant_supported: Incomplete
class ActivationQuantPattern(ABC, metaclass=abc.ABCMeta):
quant_key: Incomplete
quant_dtype: Incomplete
QUANT_OP: Incomplete
FUSED_OP: Incomplete
silu_and_mul_matcher: Incomplete
def __init__(self, quant_key: QuantKey) -> None: ...
def empty_quant(self, *args: Any, **kwargs: Any) -> torch.Tensor: ...
@abstractmethod
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class SiluMulFp8StaticQuantPattern(ActivationQuantPattern):
quant_matcher: Incomplete
def __init__(self) -> None: ...
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class SiluMulNvfp4QuantPattern(ActivationQuantPattern):
def __init__(self) -> None: ...
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class ActivationQuantFusionPass(VllmPatternMatcherPass):
patterns: PatternMatcherPass
@enable_fake_mode
def __init__(self, config: VllmConfig) -> None: ...
matched_count: Incomplete
@VllmInductorPass.time_and_log
def __call__(self, graph: torch.fx.Graph) -> None: ...
def uuid(self) -> str: ...
@@ -0,0 +1,201 @@
import torch
import torch.fx as fx
from ..inductor_pass import enable_fake_mode as enable_fake_mode
from ..vllm_inductor_pass import (
VllmInductorPass as VllmInductorPass,
VllmPatternMatcherPass as VllmPatternMatcherPass,
)
from .matcher_utils import (
MatcherFusedAddRMSNorm as MatcherFusedAddRMSNorm,
MatcherQuantFP8 as MatcherQuantFP8,
MatcherRMSNorm as MatcherRMSNorm,
)
from _typeshed import Incomplete
from torch._inductor.pattern_matcher import PatternMatcherPass
from types import ModuleType
from vllm.config import VllmConfig as VllmConfig
from vllm.config.utils import Range as Range
from vllm.distributed import (
get_tp_group as get_tp_group,
tensor_model_parallel_all_reduce as tensor_model_parallel_all_reduce,
)
from vllm.distributed.device_communicators.flashinfer_all_reduce import (
destroy_fi_ar_workspace as destroy_fi_ar_workspace,
get_fi_ar_quant_workspace as get_fi_ar_quant_workspace,
get_fi_ar_workspace as get_fi_ar_workspace,
initialize_fi_ar_quant_workspace as initialize_fi_ar_quant_workspace,
initialize_fi_ar_workspace as initialize_fi_ar_workspace,
)
from vllm.distributed.parallel_state import (
get_tensor_model_parallel_rank as get_tensor_model_parallel_rank,
get_tensor_model_parallel_world_size as get_tensor_model_parallel_world_size,
)
from vllm.logger import init_logger as init_logger
from vllm.model_executor.layers.quantization.utils.quant_utils import (
kFp8StaticTensorSym as kFp8StaticTensorSym,
)
from vllm.platforms import current_platform as current_platform
from vllm.utils.torch_utils import (
direct_register_custom_op as direct_register_custom_op,
)
FP8_DTYPE: Incomplete
logger: Incomplete
flashinfer_comm: ModuleType | None
STATIC_FP4_QUANT_OP: Incomplete
FI_ALLREDUCE_FUSION_MAX_SIZE_MB: dict[int, dict[int, float]]
ar_fusion_patterns: Incomplete
MiB: Incomplete
def call_trtllm_fused_allreduce_norm(
allreduce_in: torch.Tensor,
residual: torch.Tensor,
rms_gamma: torch.Tensor,
rms_eps: float,
world_size: int,
launch_with_pdl: bool,
fp32_acc: bool,
max_token_num: int,
pattern_code: int,
norm_out: torch.Tensor | None = None,
quant_out: torch.Tensor | None = None,
scale_out: torch.Tensor | None = None,
scale_factor: torch.Tensor | None = None,
) -> None: ...
def call_trtllm_fused_allreduce_norm_fake(
allreduce_in: torch.Tensor,
residual: torch.Tensor,
rms_gamma: torch.Tensor,
rms_eps: float,
world_size: int,
launch_with_pdl: bool,
fp32_acc: bool,
max_token_num: int,
pattern_code: int,
norm_out: torch.Tensor | None = None,
quant_out: torch.Tensor | None = None,
scale_out: torch.Tensor | None = None,
scale_factor: torch.Tensor | None = None,
) -> None: ...
flashinfer_trtllm_fused_allreduce_norm: Incomplete
class FlashInferFusedAllReduceParams:
world_size: Incomplete
launch_with_pdl: bool
fp32_acc: bool
max_token_num: Incomplete
def __init__(self, world_size: int, max_token_num: int = 1024) -> None: ...
def get_trtllm_fused_allreduce_kwargs(self) -> dict[str, bool | int]: ...
class BasePattern:
dtype: Incomplete
device: Incomplete
tp: Incomplete
tp_size: Incomplete
def __init__(self, dtype: torch.dtype, device: str | None) -> None: ...
class AllReduceRMSNormPattern(BasePattern):
epsilon: Incomplete
allreduce_params: Incomplete
rmsnorm_matcher: Incomplete
def __init__(
self,
epsilon: float,
dtype: torch.dtype,
device: str | None,
allreduce_params: FlashInferFusedAllReduceParams,
) -> None: ...
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class AllReduceFusedAddRMSNormPattern(BasePattern):
epsilon: Incomplete
allreduce_params: Incomplete
rmsnorm_matcher: Incomplete
def __init__(
self,
epsilon: float,
dtype: torch.dtype,
device: str | None,
allreduce_params: FlashInferFusedAllReduceParams,
) -> None: ...
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class AllReduceFusedRMSNormStaticQuantFP8Pattern(BasePattern):
epsilon: Incomplete
allreduce_params: Incomplete
quant_dtype: Incomplete
rmsnorm_matcher: Incomplete
quant_matcher: Incomplete
def __init__(
self,
epsilon: float,
dtype: torch.dtype,
device: str | None,
allreduce_params: FlashInferFusedAllReduceParams,
) -> None: ...
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class AllReduceFusedAddRMSNormStaticQuantFP8Pattern(BasePattern):
epsilon: Incomplete
allreduce_params: Incomplete
quant_dtype: Incomplete
rmsnorm_matcher: Incomplete
quant_matcher: Incomplete
def __init__(
self,
epsilon: float,
dtype: torch.dtype,
device: str | None,
allreduce_params: FlashInferFusedAllReduceParams,
) -> None: ...
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class AllReduceFusedRMSNormStaticQuantNVFP4Pattern(BasePattern):
epsilon: Incomplete
allreduce_params: Incomplete
rmsnorm_matcher: Incomplete
def __init__(
self,
epsilon: float,
dtype: torch.dtype,
device: str | None,
allreduce_params: FlashInferFusedAllReduceParams,
) -> None: ...
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class AllReduceFusedAddRMSNormStaticQuantNVFP4Pattern(BasePattern):
epsilon: Incomplete
allreduce_params: Incomplete
rmsnorm_matcher: Incomplete
def __init__(
self,
epsilon: float,
dtype: torch.dtype,
device: str | None,
allreduce_params: FlashInferFusedAllReduceParams,
) -> None: ...
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class AllReduceFusionPass(VllmPatternMatcherPass):
disabled: bool
tp_size: Incomplete
patterns: PatternMatcherPass
hidden_dim: Incomplete
group: Incomplete
max_token_num: Incomplete
allreduce_params: Incomplete
def __init__(self, config: VllmConfig) -> None: ...
@enable_fake_mode
def register_patterns(self) -> None: ...
def is_applicable_for_range(self, compile_range: Range) -> bool: ...
matched_count: Incomplete
@VllmInductorPass.time_and_log
def __call__(self, graph: fx.Graph) -> None: ...
def __del__(self) -> None: ...
@@ -0,0 +1,84 @@
import abc
import torch
from ..fx_utils import is_func as is_func
from ..inductor_pass import enable_fake_mode as enable_fake_mode
from ..vllm_inductor_pass import (
VllmInductorPass as VllmInductorPass,
VllmPatternMatcherPass as VllmPatternMatcherPass,
)
from .matcher_utils import MatcherQuantFP8 as MatcherQuantFP8
from .rms_quant_fusion import (
QUANT_OPS as QUANT_OPS,
empty_bf16 as empty_bf16,
empty_fp32 as empty_fp32,
empty_i32 as empty_i32,
)
from _typeshed import Incomplete
from abc import ABC
from collections.abc import Callable as Callable
from torch import fx as fx
from torch._inductor.pattern_matcher import PatternMatcherPass
from typing import Any, ParamSpec
from vllm.config import (
VllmConfig as VllmConfig,
get_layers_from_vllm_config as get_layers_from_vllm_config,
)
from vllm.logger import init_logger as init_logger
from vllm.model_executor.layers.attention import Attention as Attention
from vllm.model_executor.layers.quantization.utils.quant_utils import (
QuantKey as QuantKey,
kNvfp4Dynamic as kNvfp4Dynamic,
kStaticTensorScale as kStaticTensorScale,
)
from vllm.platforms import current_platform as current_platform
from vllm.utils.math_utils import round_up as round_up
logger: Incomplete
P = ParamSpec("P")
FP8_DTYPE: Incomplete
FP4_DTYPE: Incomplete
ATTN_OP: Incomplete
RESHAPE_OP: Incomplete
class AttentionQuantPattern(ABC, metaclass=abc.ABCMeta):
layer: Incomplete
layer_name: Incomplete
num_heads: Incomplete
head_size: Incomplete
quant_key: Incomplete
quant_dtype: Incomplete
dtype: Incomplete
QUANT_OP: Incomplete
def __init__(
self, layer: Attention, quant_key: QuantKey, dtype: torch.dtype
) -> None: ...
def empty(self, *args: Any, **kwargs: Any) -> torch.Tensor: ...
def empty_quant(self, *args: Any, **kwargs: Any) -> torch.Tensor: ...
@staticmethod
def wrap_trace_fn(
trace_fn: Callable[P, fx.GraphModule],
*process_fx_fns: Callable[[fx.GraphModule], None],
) -> Callable[P, fx.GraphModule]: ...
@staticmethod
def fx_view_to_reshape(gm: torch.fx.GraphModule) -> None: ...
@staticmethod
def remove_noop_permutes(gm: torch.fx.GraphModule) -> None: ...
def register_if_supported(self, pm_pass: PatternMatcherPass) -> None: ...
class AttentionFp8StaticQuantPattern(AttentionQuantPattern):
quant_matcher: Incomplete
def __init__(
self, layer: Attention, dtype: torch.dtype, symmetric: bool = True
) -> None: ...
class AttentionNvfp4QuantPattern(AttentionQuantPattern):
def __init__(self, layer: Attention, dtype: torch.dtype) -> None: ...
class AttnFusionPass(VllmPatternMatcherPass):
patterns: Incomplete
@enable_fake_mode
def __init__(self, config: VllmConfig) -> None: ...
matched_count: Incomplete
@VllmInductorPass.time_and_log
def __call__(self, graph: torch.fx.graph.Graph) -> None: ...
def uuid(self) -> str: ...
@@ -0,0 +1,60 @@
import torch
import torch.fx as fx
from ..inductor_pass import enable_fake_mode as enable_fake_mode
from ..vllm_inductor_pass import (
VllmInductorPass as VllmInductorPass,
VllmPatternMatcherPass as VllmPatternMatcherPass,
)
from _typeshed import Incomplete
from torch._inductor.pattern_matcher import PatternMatcherPass
from vllm.config import VllmConfig as VllmConfig
from vllm.config.utils import Range as Range
from vllm.distributed import get_tp_group as get_tp_group
from vllm.distributed.parallel_state import (
get_tensor_model_parallel_world_size as get_tensor_model_parallel_world_size,
)
from vllm.logger import init_logger as init_logger
from vllm.platforms import current_platform as current_platform
FP8_DTYPE: Incomplete
logger: Incomplete
class BasePattern:
dtype: Incomplete
device: Incomplete
tp: Incomplete
tp_size: Incomplete
def __init__(self, dtype: torch.dtype, device: str | None) -> None: ...
class GEMMReduceScatterPattern(BasePattern):
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class AllGatherGEMMPattern(BasePattern):
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class ScaledMMReduceScatterPattern(BasePattern):
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class AllGatherScaledMMPattern(BasePattern):
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class CutlassScaledMMReduceScatterPattern(BasePattern):
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class AllGatherCutlassScaledMMPattern(BasePattern):
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class AsyncTPPass(VllmPatternMatcherPass):
patterns: PatternMatcherPass
@enable_fake_mode
def __init__(self, config: VllmConfig) -> None: ...
def is_applicable_for_range(self, compile_range: Range) -> bool: ...
matched_count: Incomplete
@VllmInductorPass.time_and_log
def __call__(self, graph: fx.Graph) -> None: ...
@@ -0,0 +1,160 @@
import abc
import torch
from _typeshed import Incomplete
from abc import ABC, abstractmethod
from torch._ops import OpOverload as OpOverload
from typing import Any
from vllm._aiter_ops import rocm_aiter_ops as rocm_aiter_ops
from vllm.config import get_current_vllm_config as get_current_vllm_config
from vllm.model_executor.layers.activation import SiluAndMul as SiluAndMul
from vllm.model_executor.layers.layernorm import RMSNorm as RMSNorm
from vllm.model_executor.layers.quantization.input_quant_fp8 import QuantFP8 as QuantFP8
from vllm.model_executor.layers.quantization.utils.quant_utils import (
GroupShape as GroupShape,
QuantKey as QuantKey,
kFp8Dynamic128Sym as kFp8Dynamic128Sym,
kFp8Dynamic64Sym as kFp8Dynamic64Sym,
kFp8DynamicTensorSym as kFp8DynamicTensorSym,
kFp8DynamicTokenSym as kFp8DynamicTokenSym,
kFp8StaticTensorSym as kFp8StaticTensorSym,
kNvfp4Dynamic as kNvfp4Dynamic,
)
from vllm.model_executor.layers.rotary_embedding import (
RotaryEmbedding as RotaryEmbedding,
)
from vllm.platforms import current_platform as current_platform
RMS_OP: Incomplete
RMS_ADD_OP: Incomplete
ROTARY_OP: Incomplete
FLASHINFER_ROTARY_OP: Incomplete
QUANT_OPS: dict[QuantKey, OpOverload]
SILU_MUL_OP: Incomplete
class MatcherCustomOp(ABC, metaclass=abc.ABCMeta):
model_dtype: Incomplete
device: Incomplete
enabled: Incomplete
forward: Incomplete
def __init__(self, enabled: bool) -> None: ...
@abstractmethod
def forward_custom(self, *args: Any, **kwargs: Any) -> Any: ...
@abstractmethod
def forward_native(self, *args: Any, **kwargs: Any) -> Any: ...
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
def empty(self, *args: Any, **kwargs: Any) -> torch.Tensor: ...
def empty_int64(self, *args: Any, **kwargs: Any) -> torch.Tensor: ...
def empty_f32(self, *args: Any, **kwargs: Any) -> torch.Tensor: ...
def inputs(self) -> list[torch.Tensor]: ...
class MatcherRotaryEmbedding(MatcherCustomOp):
is_neox: Incomplete
head_size: Incomplete
num_heads: Incomplete
num_kv_heads: Incomplete
q_size: Incomplete
kv_size: Incomplete
rotary_dim: Incomplete
rotary_op: Incomplete
def __init__(
self,
is_neox: bool,
head_size: int,
num_heads: int,
num_kv_heads: int,
use_flashinfer: bool = False,
match_rocm_aiter: bool | None = None,
enabled: bool | None = None,
) -> None: ...
def inputs(self) -> list[torch.Tensor]: ...
def forward_custom(
self,
positions: torch.Tensor,
query: torch.Tensor,
key: torch.Tensor | None,
cos_sin_cache: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor | None]: ...
def forward_native(
self,
positions: torch.Tensor,
query: torch.Tensor,
key: torch.Tensor | None,
cos_sin_cache: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor | None]: ...
class MatcherRMSNorm(MatcherCustomOp):
epsilon: Incomplete
match_rocm_aiter: Incomplete
def __init__(
self,
epsilon: float,
enabled: bool | None = None,
match_rocm_aiter: bool = False,
) -> None: ...
def inputs(self) -> list[torch.Tensor]: ...
def forward_rocm_aiter(
self, input: torch.Tensor, weight: torch.Tensor
) -> torch.Tensor: ...
def forward_custom(
self, input: torch.Tensor, weight: torch.Tensor
) -> torch.Tensor: ...
def forward_native(
self, input: torch.Tensor, weight: torch.Tensor
) -> torch.Tensor: ...
class MatcherFusedAddRMSNorm(MatcherCustomOp):
epsilon: Incomplete
match_rocm_aiter: Incomplete
def __init__(
self,
epsilon: float,
enabled: bool | None = None,
match_rocm_aiter: bool = False,
) -> None: ...
def inputs(self) -> list[torch.Tensor]: ...
def forward_rocm_aiter(
self, input: torch.Tensor, weight: torch.Tensor, residual: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor]: ...
def forward_custom(
self, input: torch.Tensor, weight: torch.Tensor, residual: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor]: ...
def forward_native(
self, input: torch.Tensor, weight: torch.Tensor, residual: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor]: ...
class MatcherQuantFP8(MatcherCustomOp):
quant_key: Incomplete
has_col_major_scales: Incomplete
is_e8m0: Incomplete
match_rocm_aiter: Incomplete
is_tma_aligned: Incomplete
QUANT_OP: Incomplete
quant_fp8: Incomplete
def __init__(
self,
quant_key: QuantKey,
enabled: bool | None = None,
has_col_major_scales: bool = False,
is_e8m0: bool = False,
match_rocm_aiter: bool = False,
is_tma_aligned: bool = False,
) -> None: ...
def forward_rocm_aiter(
self, input: torch.Tensor, scale: torch.Tensor | None = None
) -> tuple[torch.Tensor, torch.Tensor]: ...
def forward_custom(
self, input: torch.Tensor, scale: torch.Tensor | None = None
) -> tuple[torch.Tensor, torch.Tensor]: ...
def forward_native(
self, input: torch.Tensor, scale: torch.Tensor | None = None
) -> tuple[torch.Tensor, torch.Tensor]: ...
def make_scale(
self, input: torch.Tensor, transposed: bool = False
) -> torch.Tensor: ...
def inputs(self) -> list[torch.Tensor]: ...
class MatcherSiluAndMul(MatcherCustomOp):
def __init__(self, enabled: bool | None = None) -> None: ...
def inputs(self) -> list[torch.Tensor]: ...
def forward_custom(self, x: torch.Tensor) -> torch.Tensor: ...
def forward_native(self, x: torch.Tensor) -> torch.Tensor: ...
@@ -0,0 +1,72 @@
import torch
from ..inductor_pass import enable_fake_mode as enable_fake_mode
from ..vllm_inductor_pass import (
VllmInductorPass as VllmInductorPass,
VllmPatternMatcherPass as VllmPatternMatcherPass,
)
from .matcher_utils import (
MatcherRMSNorm as MatcherRMSNorm,
MatcherRotaryEmbedding as MatcherRotaryEmbedding,
)
from .rms_quant_fusion import (
empty_bf16 as empty_bf16,
empty_fp32 as empty_fp32,
empty_i64 as empty_i64,
)
from _typeshed import Incomplete
from collections.abc import Callable as Callable
from torch import fx as fx
from torch._inductor.pattern_matcher import PatternMatcherPass
from typing import ParamSpec
from vllm.config import (
VllmConfig as VllmConfig,
get_layers_from_vllm_config as get_layers_from_vllm_config,
)
from vllm.logger import init_logger as init_logger
from vllm.model_executor.layers.attention import Attention as Attention
from vllm.model_executor.layers.rotary_embedding import (
RotaryEmbedding as RotaryEmbedding,
)
logger: Incomplete
FUSED_QK_ROPE_OP: Incomplete
P = ParamSpec("P")
class QkNormRopePattern:
num_heads: Incomplete
num_kv_heads: Incomplete
head_dim: Incomplete
q_size: Incomplete
kv_size: Incomplete
eps: Incomplete
rmsnorm_matcher: Incomplete
is_neox: Incomplete
rope_flashinfer: Incomplete
rope_matcher: Incomplete
def __init__(
self,
head_dim: int,
num_heads: int,
num_kv_heads: int,
eps: float,
is_neox: bool,
rope_flashinfer: bool = False,
) -> None: ...
def get_inputs(self) -> list[torch.Tensor]: ...
@staticmethod
def wrap_trace_fn(
trace_fn: Callable[P, fx.GraphModule],
*process_fx_fns: Callable[[fx.GraphModule], None],
) -> Callable[P, fx.GraphModule]: ...
@staticmethod
def fx_view_to_reshape(gm: torch.fx.GraphModule) -> None: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class QKNormRoPEFusionPass(VllmPatternMatcherPass):
patterns: PatternMatcherPass
@enable_fake_mode
def __init__(self, config: VllmConfig) -> None: ...
matched_count: Incomplete
@VllmInductorPass.time_and_log
def __call__(self, graph: fx.Graph) -> None: ...
def uuid(self) -> str: ...
@@ -0,0 +1,143 @@
import torch
from ..inductor_pass import enable_fake_mode as enable_fake_mode
from ..vllm_inductor_pass import (
VllmInductorPass as VllmInductorPass,
VllmPatternMatcherPass as VllmPatternMatcherPass,
)
from .matcher_utils import (
MatcherFusedAddRMSNorm as MatcherFusedAddRMSNorm,
MatcherQuantFP8 as MatcherQuantFP8,
MatcherRMSNorm as MatcherRMSNorm,
)
from _typeshed import Incomplete
from torch import fx as fx
from torch._inductor.pattern_matcher import PatternMatcherPass
from torch._ops import OpOverload as OpOverload
from typing import Any, NamedTuple
from vllm.config import (
VllmConfig as VllmConfig,
get_current_vllm_config as get_current_vllm_config,
)
from vllm.logger import init_logger as init_logger
from vllm.model_executor.layers.quantization.utils.quant_utils import (
GroupShape as GroupShape,
QuantKey as QuantKey,
ScaleDesc as ScaleDesc,
kFp8Dynamic128Sym as kFp8Dynamic128Sym,
kFp8Dynamic64Sym as kFp8Dynamic64Sym,
kFp8DynamicTensorSym as kFp8DynamicTensorSym,
kFp8DynamicTokenSym as kFp8DynamicTokenSym,
kFp8StaticTensorSym as kFp8StaticTensorSym,
kNvfp4Dynamic as kNvfp4Dynamic,
kStaticTensorScale as kStaticTensorScale,
)
from vllm.platforms import current_platform as current_platform
logger: Incomplete
FP8_DTYPE: Incomplete
FP4_DTYPE: Incomplete
def empty_bf16(*args: Any, **kwargs: Any) -> torch.Tensor: ...
def empty_fp32(*args: Any, **kwargs: Any) -> torch.Tensor: ...
def empty_i32(*args: Any, **kwargs: Any) -> torch.Tensor: ...
def empty_i64(*args: Any, **kwargs: Any) -> torch.Tensor: ...
RMS_OP: Incomplete
RMS_ADD_OP: Incomplete
QUANT_OPS: dict[QuantKey, OpOverload]
class FusedRMSQuantKey(NamedTuple):
quant: QuantKey
fused_add: bool
FUSED_OPS: dict[FusedRMSQuantKey, OpOverload]
class RMSNormQuantPattern:
epsilon: Incomplete
quant_dtype: Incomplete
model_dtype: Incomplete
FUSED_OP: Incomplete
rmsnorm_matcher: Incomplete
quant_matcher: Incomplete
def __init__(
self,
epsilon: float,
key: FusedRMSQuantKey,
has_col_major_scales: bool = False,
is_e8m0: bool = False,
is_tma_aligned: bool = False,
) -> None: ...
class RMSNormStaticQuantPattern(RMSNormQuantPattern):
def __init__(
self, epsilon: float, quant_dtype: torch.dtype, symmetric: bool = True
) -> None: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class FusedAddRMSNormStaticQuantPattern(RMSNormQuantPattern):
def __init__(
self, epsilon: float, quant_dtype: torch.dtype, symmetric: bool = True
) -> None: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class FusedAddRMSNormGroupQuantPattern(RMSNormQuantPattern):
group_shape: Incomplete
is_e8m0: Incomplete
has_col_major_scales: Incomplete
is_tma_aligned: Incomplete
def __init__(
self,
epsilon: float,
quant_dtype: torch.dtype,
group_shape: GroupShape,
symmetric: bool = True,
is_e8m0: bool = False,
has_col_major_scales: bool = True,
is_tma_aligned: bool = True,
) -> None: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class RMSNormGroupQuantPattern(RMSNormQuantPattern):
group_shape: Incomplete
has_col_major_scales: Incomplete
is_tma_aligned: Incomplete
def __init__(
self,
epsilon: float,
quant_dtype: torch.dtype,
group_shape: GroupShape,
symmetric: bool = True,
is_e8m0: bool = False,
has_col_major_scales: bool = True,
is_tma_aligned: bool = True,
) -> None: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class RMSNormDynamicQuantPattern(RMSNormQuantPattern):
def __init__(
self,
epsilon: float,
quant_dtype: torch.dtype,
group_shape: GroupShape = ...,
symmetric: bool = True,
) -> None: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class FusedAddRMSNormDynamicQuantPattern(RMSNormQuantPattern):
def __init__(
self,
epsilon: float,
quant_dtype: torch.dtype,
group_shape: GroupShape = ...,
symmetric: bool = True,
) -> None: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class RMSNormQuantFusionPass(VllmPatternMatcherPass):
patterns: PatternMatcherPass
@enable_fake_mode
def __init__(self, config: VllmConfig) -> None: ...
matched_count: Incomplete
@VllmInductorPass.time_and_log
def __call__(self, graph: fx.Graph) -> None: ...
def uuid(self) -> str: ...
@@ -0,0 +1,133 @@
import torch
from ..inductor_pass import enable_fake_mode as enable_fake_mode
from ..vllm_inductor_pass import (
VllmInductorPass as VllmInductorPass,
VllmPatternMatcherPass as VllmPatternMatcherPass,
)
from .act_quant_fusion import ActivationQuantPattern as ActivationQuantPattern
from .matcher_utils import (
MatcherFusedAddRMSNorm as MatcherFusedAddRMSNorm,
MatcherQuantFP8 as MatcherQuantFP8,
MatcherRMSNorm as MatcherRMSNorm,
MatcherSiluAndMul as MatcherSiluAndMul,
)
from .rms_quant_fusion import FusedRMSQuantKey as FusedRMSQuantKey
from _typeshed import Incomplete
from torch import fx as fx
from torch._inductor.pattern_matcher import PatternMatcherPass
from vllm._aiter_ops import rocm_aiter_ops as rocm_aiter_ops
from vllm.config import VllmConfig as VllmConfig
from vllm.logger import init_logger as init_logger
from vllm.model_executor.layers.quantization.utils.quant_utils import (
GroupShape as GroupShape,
QuantKey as QuantKey,
ScaleDesc as ScaleDesc,
kFp8Dynamic128Sym as kFp8Dynamic128Sym,
)
from vllm.platforms import current_platform as current_platform
logger: Incomplete
FP8_DTYPE: Incomplete
class AiterRMSNormQuantPattern:
epsilon: Incomplete
quant_dtype: Incomplete
rmsnorm_matcher: Incomplete
quant_matcher: Incomplete
def __init__(
self, epsilon: float, key: FusedRMSQuantKey, match_aiter_quant: bool = True
) -> None: ...
class AiterRMSNormDynamicQuantPattern(AiterRMSNormQuantPattern):
FUSED_OP: Incomplete
def __init__(
self,
epsilon: float,
quant_dtype: torch.dtype,
match_aiter_quant: bool = True,
group_shape: GroupShape = ...,
symmetric: bool = True,
) -> None: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class AiterFusedAddRMSNormDynamicQuantPattern(AiterRMSNormQuantPattern):
FUSED_OP: Incomplete
def __init__(
self,
epsilon: float,
quant_dtype: torch.dtype,
match_aiter_quant: bool = True,
group_shape: GroupShape = ...,
symmetric: bool = True,
) -> None: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class AiterRMSFp8GroupQuantPattern(AiterRMSNormQuantPattern):
FUSED_OP: Incomplete
def __init__(
self,
epsilon: float,
quant_dtype: torch.dtype,
group_shape: GroupShape,
match_aiter_quant: bool = True,
symmetric: bool = True,
) -> None: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class AiterFusedAddRMSFp8GroupQuantPattern(AiterRMSNormQuantPattern):
FUSED_OP: Incomplete
def __init__(
self,
epsilon: float,
quant_dtype: torch.dtype,
group_shape: GroupShape,
match_aiter_quant: bool = True,
symmetric: bool = True,
) -> None: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class RocmAiterRMSNormQuantFusionPass(VllmPatternMatcherPass):
patterns: PatternMatcherPass
@enable_fake_mode
def __init__(self, config: VllmConfig) -> None: ...
matched_count: Incomplete
@VllmInductorPass.time_and_log
def __call__(self, graph: fx.Graph) -> None: ...
def uuid(self) -> str: ...
class AiterSiluMulFp8GroupQuantPattern(ActivationQuantPattern):
FUSED_SILU_MUL_QUANT_OP: Incomplete
silu_and_mul_matcher: Incomplete
quant_matcher: Incomplete
def __init__(self) -> None: ...
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class RocmAiterSiluMulFp8GroupQuantFusionPass(VllmPatternMatcherPass):
patterns: PatternMatcherPass
@enable_fake_mode
def __init__(self, config: VllmConfig) -> None: ...
matched_count: Incomplete
@VllmInductorPass.time_and_log
def __call__(self, graph: torch.fx.Graph) -> None: ...
def uuid(self) -> str: ...
class AddAiterRMSNormPadPattern:
AITER_TRITON_ADD_RMSNORM_PAD_OP: Incomplete
epsilon: Incomplete
hidden_size: Incomplete
x_pad_to_multiple: Incomplete
rmsnorm_matcher: Incomplete
def __init__(
self, epsilon: float, hidden_size: int, x_pad_to_multiple: int
) -> None: ...
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class RocmAiterTritonAddRMSNormPadFusionPass(VllmPatternMatcherPass):
patterns: PatternMatcherPass
def __init__(self, config: VllmConfig) -> None: ...
matched_count: Incomplete
@VllmInductorPass.time_and_log
def __call__(self, graph: torch.fx.Graph) -> None: ...
def uuid(self) -> str: ...
@@ -0,0 +1,72 @@
import torch
from ..inductor_pass import enable_fake_mode as enable_fake_mode
from ..vllm_inductor_pass import (
VllmInductorPass as VllmInductorPass,
VllmPatternMatcherPass as VllmPatternMatcherPass,
)
from .matcher_utils import MatcherRotaryEmbedding as MatcherRotaryEmbedding
from .rms_quant_fusion import empty_bf16 as empty_bf16, empty_i64 as empty_i64
from _typeshed import Incomplete
from torch import fx as fx
from torch._inductor.pattern_matcher import PatternMatcherPass
from vllm.config import (
VllmConfig as VllmConfig,
get_layers_from_vllm_config as get_layers_from_vllm_config,
)
from vllm.config.utils import Range as Range
from vllm.logger import init_logger as init_logger
from vllm.model_executor.layers.attention.attention import (
Attention as Attention,
get_attention_context as get_attention_context,
)
from vllm.utils.torch_utils import (
direct_register_custom_op as direct_register_custom_op,
)
logger: Incomplete
def fused_rope_and_unified_kv_cache_update_impl(
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
positions: torch.Tensor,
cos_sin_cache: torch.Tensor,
is_neox: bool,
layer_name: str = "",
) -> torch.Tensor: ...
def fused_rope_and_unified_kv_cache_update_fake(
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
positions: torch.Tensor,
cos_sin_cache: torch.Tensor,
is_neox: bool,
layer_name: str = "",
) -> torch.Tensor: ...
class RopeReshapeKVCachePattern:
FUSED_OP: Incomplete
layer_name: Incomplete
num_heads: Incomplete
num_kv_heads: Incomplete
head_size: Incomplete
head_size_v: Incomplete
is_neox: Incomplete
q_size: Incomplete
k_size: Incomplete
v_size: Incomplete
rope_matcher: Incomplete
def __init__(self, layer: Attention, is_neox: bool) -> None: ...
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class RopeKVCacheFusionPass(VllmPatternMatcherPass):
patterns: PatternMatcherPass
max_token_num: Incomplete
@enable_fake_mode
def __init__(self, config: VllmConfig) -> None: ...
matched_count: Incomplete
@VllmInductorPass.time_and_log
def __call__(self, graph: fx.Graph) -> None: ...
def is_applicable_for_range(self, compile_range: Range) -> bool: ...
def uuid(self) -> str: ...
@@ -0,0 +1,95 @@
import torch
import torch.fx as fx
from ..inductor_pass import enable_fake_mode as enable_fake_mode
from ..utility.noop_elimination import NoOpEliminationPass as NoOpEliminationPass
from ..vllm_inductor_pass import (
VllmInductorPass as VllmInductorPass,
VllmPatternMatcherPass as VllmPatternMatcherPass,
)
from .matcher_utils import (
MatcherFusedAddRMSNorm as MatcherFusedAddRMSNorm,
MatcherQuantFP8 as MatcherQuantFP8,
MatcherRMSNorm as MatcherRMSNorm,
)
from _typeshed import Incomplete
from collections.abc import Callable as Callable, Sequence
from torch._inductor.pattern_matcher import PatternMatcherPass
from vllm.config import VllmConfig as VllmConfig
from vllm.config.utils import Range as Range
from vllm.distributed import (
get_tp_group as get_tp_group,
tensor_model_parallel_all_reduce as tensor_model_parallel_all_reduce,
)
from vllm.distributed.parallel_state import (
get_tensor_model_parallel_world_size as get_tensor_model_parallel_world_size,
)
from vllm.logger import init_logger as init_logger
from vllm.model_executor.layers.quantization.utils.quant_utils import (
kFp8StaticTensorSym as kFp8StaticTensorSym,
)
logger: Incomplete
SP_MIN_HIDDEN_SIZE: dict[int, int]
SP_MIN_PER_GPU_SIZE_MB: dict[int, float]
def get_sequence_parallelism_threshold(
hidden_size: int, tp_size: int, element_size: int
) -> int | None: ...
def get_first_out_wrapper(
fn: Callable[..., Sequence[torch.Tensor]],
) -> Callable[..., torch.Tensor]: ...
class _SequenceParallelPatternHelper:
epsilon: Incomplete
dtype: Incomplete
device: Incomplete
tp_group: Incomplete
tp_size: Incomplete
def __init__(
self, epsilon: float, dtype: torch.dtype, device: str | None
) -> None: ...
class FirstAllReduceRMSNormPattern(_SequenceParallelPatternHelper):
rmsnorm_matcher: Incomplete
def __init__(
self, epsilon: float, dtype: torch.dtype, device: str | None
) -> None: ...
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class MiddleAllReduceRMSNormPattern(_SequenceParallelPatternHelper):
rmsnorm_matcher: Incomplete
def __init__(
self, epsilon: float, dtype: torch.dtype, device: str | None
) -> None: ...
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class FirstAllReduceRMSNormStaticFP8Pattern(_SequenceParallelPatternHelper):
rmsnorm_matcher: Incomplete
quant_matcher: Incomplete
def __init__(
self, epsilon: float, dtype: torch.dtype, device: str | None
) -> None: ...
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class MiddleAllReduceRMSNormStaticFP8Pattern(_SequenceParallelPatternHelper):
rmsnorm_matcher: Incomplete
quant_matcher: Incomplete
def __init__(
self, epsilon: float, dtype: torch.dtype, device: str | None
) -> None: ...
def get_inputs(self) -> list[torch.Tensor]: ...
def register(self, pm_pass: PatternMatcherPass) -> None: ...
class SequenceParallelismPass(VllmPatternMatcherPass):
min_token_num: Incomplete
noop_cleanup: Incomplete
patterns: PatternMatcherPass
@enable_fake_mode
def __init__(self, config: VllmConfig) -> None: ...
def is_applicable_for_range(self, compile_range: Range) -> bool: ...
matched_count: Incomplete
@VllmInductorPass.time_and_log
def __call__(self, graph: fx.Graph) -> None: ...
@@ -0,0 +1,15 @@
from collections.abc import Iterable, Iterator
from torch import fx as fx
from torch._ops import OpOverload, OpOverloadPacket
from torch.fx.node import Target as Target
def is_func(node: fx.Node, target: Target) -> bool: ...
def is_auto_func(node: fx.Node, op: OpOverload) -> bool: ...
def find_auto_fn_maybe(nodes: Iterable[fx.Node], op: OpOverload) -> fx.Node | None: ...
def find_auto_fn(nodes: Iterable[fx.Node], op: OpOverload) -> fx.Node: ...
def find_getitem_maybe(node: fx.Node, idx: int) -> fx.Node | None: ...
def find_getitem(node: fx.Node, idx: int) -> fx.Node: ...
def find_op_nodes(
op: OpOverload | OpOverloadPacket, graph: fx.Graph
) -> Iterator[fx.Node]: ...
def get_only_user(node: fx.Node) -> fx.Node: ...
@@ -0,0 +1,37 @@
import torch
from _typeshed import Incomplete
from collections.abc import Callable as Callable, Generator
from contextlib import contextmanager
from torch import fx as fx
from torch._inductor.custom_graph_pass import CustomGraphPass
from typing import Any, ParamSpec, TypeVar
from vllm.config.utils import Range as Range
P = ParamSpec("P")
R = TypeVar("R")
class PassContext:
compile_range: Range
def __init__(self, compile_range: Range) -> None: ...
def get_pass_context() -> PassContext: ...
@contextmanager
def pass_context(compile_range: Range) -> Generator[None, None, None]: ...
class InductorPass(CustomGraphPass):
def uuid(self) -> str: ...
@staticmethod
def hash_source(*srcs: str | Any) -> str: ...
@staticmethod
def hash_dict(dict_: dict[Any, Any]) -> str: ...
def is_applicable_for_range(self, compile_range: Range) -> bool: ...
class CallableInductorPass(InductorPass):
callable: Incomplete
def __init__(
self, callable: Callable[[fx.Graph], None], uuid: Any | None = None
) -> None: ...
def __call__(self, graph: torch.fx.Graph) -> None: ...
def uuid(self) -> Any: ...
def enable_fake_mode(fn: Callable[P, R]) -> Callable[P, R]: ...
@@ -0,0 +1,65 @@
from .fusion.act_quant_fusion import (
ActivationQuantFusionPass as ActivationQuantFusionPass,
)
from .fusion.allreduce_rms_fusion import AllReduceFusionPass as AllReduceFusionPass
from .fusion.attn_quant_fusion import AttnFusionPass as AttnFusionPass
from .fusion.collective_fusion import AsyncTPPass as AsyncTPPass
from .fusion.qk_norm_rope_fusion import QKNormRoPEFusionPass as QKNormRoPEFusionPass
from .fusion.rms_quant_fusion import RMSNormQuantFusionPass as RMSNormQuantFusionPass
from .fusion.rocm_aiter_fusion import (
RocmAiterRMSNormQuantFusionPass as RocmAiterRMSNormQuantFusionPass,
RocmAiterSiluMulFp8GroupQuantFusionPass as RocmAiterSiluMulFp8GroupQuantFusionPass,
RocmAiterTritonAddRMSNormPadFusionPass as RocmAiterTritonAddRMSNormPadFusionPass,
)
from .fusion.rope_kvcache_fusion import RopeKVCacheFusionPass as RopeKVCacheFusionPass
from .fusion.sequence_parallelism import (
SequenceParallelismPass as SequenceParallelismPass,
)
from .inductor_pass import (
CustomGraphPass as CustomGraphPass,
InductorPass as InductorPass,
get_pass_context as get_pass_context,
)
from .utility.fix_functionalization import (
FixFunctionalizationPass as FixFunctionalizationPass,
)
from .utility.noop_elimination import NoOpEliminationPass as NoOpEliminationPass
from .utility.scatter_split_replace import (
ScatterSplitReplacementPass as ScatterSplitReplacementPass,
)
from .utility.split_coalescing import SplitCoalescingPass as SplitCoalescingPass
from .vllm_inductor_pass import VllmInductorPass as VllmInductorPass
from _typeshed import Incomplete
from collections.abc import Callable as Callable
from torch import fx as fx
from typing import ParamSpec, TypeVar
from vllm import envs as envs
from vllm._aiter_ops import rocm_aiter_ops as rocm_aiter_ops
from vllm.compilation.passes.utility.post_cleanup import (
PostCleanupPass as PostCleanupPass,
)
from vllm.config import (
VllmConfig as VllmConfig,
set_current_vllm_config as set_current_vllm_config,
)
from vllm.logger import init_logger as init_logger
from vllm.platforms import current_platform as current_platform
from vllm.utils.system_utils import set_env_var as set_env_var
logger: Incomplete
P = ParamSpec("P")
R = TypeVar("R")
def with_pattern_match_debug(fn: Callable[P, R]) -> Callable[P, R]: ...
class PostGradPassManager(CustomGraphPass):
passes: list[InductorPass]
def __init__(self) -> None: ...
@with_pattern_match_debug
def __call__(self, graph: fx.Graph) -> None: ...
pass_config: Incomplete
post_cleanup: Incomplete
fix_functionalization: Incomplete
def configure(self, config: VllmConfig) -> None: ...
def add(self, pass_: InductorPass) -> None: ...
def uuid(self) -> str: ...
@@ -0,0 +1,30 @@
import torch
from ..fx_utils import is_func as is_func
from ..vllm_inductor_pass import VllmInductorPass as VllmInductorPass
from _typeshed import Incomplete
from vllm.logger import init_logger as init_logger
from vllm.platforms import current_platform as current_platform
logger: Incomplete
class FixFunctionalizationPass(VllmInductorPass):
nodes_to_remove: list[torch.fx.Node]
@VllmInductorPass.time_and_log
def __call__(self, graph: torch.fx.Graph) -> None: ...
def defunctionalize(
self,
graph: torch.fx.Graph,
node: torch.fx.Node,
mutated_args: dict[int, torch.fx.Node | str],
args: tuple[torch.fx.Node | str, ...] | None = None,
) -> None: ...
def replace_users_with_mutated_args(
self, node: torch.fx.Node, mutated_args: dict[int, torch.fx.Node | str]
) -> None: ...
def getitem_users(self, node: torch.fx.Node) -> dict[int, torch.fx.Node]: ...
def insert_defunctionalized(
self,
graph: torch.fx.Graph,
node: torch.fx.Node,
args: tuple[torch.fx.Node | str, ...] | None = None,
) -> None: ...
@@ -0,0 +1,17 @@
import torch.fx
from ..fx_utils import is_func as is_func
from ..vllm_inductor_pass import VllmInductorPass as VllmInductorPass
from _typeshed import Incomplete
from collections.abc import Iterable
from torch import SymInt as SymInt
from vllm.logger import init_logger as init_logger
logger: Incomplete
class NoOpEliminationPass(VllmInductorPass):
@VllmInductorPass.time_and_log
def __call__(self, graph: torch.fx.Graph) -> None: ...
def dims_equivalent(self, dim: int | SymInt, i_dim: int | SymInt) -> bool: ...
def all_dims_equivalent(
self, dims: Iterable[int | SymInt], i_dims: Iterable[int | SymInt]
) -> bool: ...
@@ -0,0 +1,6 @@
from ..vllm_inductor_pass import VllmInductorPass as VllmInductorPass
from torch import fx as fx
class PostCleanupPass(VllmInductorPass):
@VllmInductorPass.time_and_log
def __call__(self, graph: fx.Graph) -> None: ...
@@ -0,0 +1,11 @@
from ..fx_utils import is_func as is_func
from ..vllm_inductor_pass import VllmInductorPass as VllmInductorPass
from _typeshed import Incomplete
from torch import fx as fx
from vllm.logger import init_logger as init_logger
logger: Incomplete
class ScatterSplitReplacementPass(VllmInductorPass):
@VllmInductorPass.time_and_log
def __call__(self, graph: fx.Graph) -> None: ...
@@ -0,0 +1,11 @@
from ..fx_utils import is_func as is_func
from ..vllm_inductor_pass import VllmInductorPass as VllmInductorPass
from _typeshed import Incomplete
from torch import fx as fx
from vllm.logger import init_logger as init_logger
logger: Incomplete
class SplitCoalescingPass(VllmInductorPass):
@VllmInductorPass.time_and_log
def __call__(self, graph: fx.Graph) -> None: ...
@@ -0,0 +1,43 @@
import torch
from .inductor_pass import InductorPass as InductorPass
from _typeshed import Incomplete
from collections.abc import Callable as Callable
from dataclasses import dataclass
from torch._inductor.pattern_matcher import PatternMatcherPass as PatternMatcherPass
from typing import ClassVar
from vllm.config import VllmConfig as VllmConfig
from vllm.logger import init_logger as init_logger
logger: Incomplete
@dataclass
class InductorCompilationConfig:
splitting_ops: list[str] | None = ...
use_inductor_graph_partition: bool = ...
class VllmInductorPass(InductorPass):
dump_prefix: ClassVar[int | None]
compilation_config: Incomplete
pass_config: Incomplete
model_dtype: Incomplete
device: str | None
pass_name: Incomplete
def __init__(self, config: VllmConfig) -> None: ...
@staticmethod
def time_and_log(
call_fn: Callable[[VllmInductorPass, torch.fx.Graph], None],
) -> Callable[[VllmInductorPass, torch.fx.Graph], None]: ...
def dump_graph(self, graph: torch.fx.Graph, stage: str) -> None: ...
def begin(self) -> None: ...
def end_and_log(self) -> None: ...
class VllmPatternMatcherPass(VllmInductorPass):
matched_count: int
def dump_patterns(
self, config: VllmConfig, pm_pass: PatternMatcherPass
) -> None: ...
class PrinterInductorPass(VllmInductorPass):
name: Incomplete
def __init__(self, name: str, config: VllmConfig) -> None: ...
def __call__(self, graph: torch.fx.Graph) -> None: ...
@@ -0,0 +1,57 @@
import dataclasses
import torch.fx as fx
from _typeshed import Incomplete
from collections.abc import Callable as Callable
from typing import Any
from vllm.compilation.backends import VllmBackend as VllmBackend
from vllm.config import VllmConfig as VllmConfig
from vllm.config.utils import Range as Range
from vllm.logger import init_logger as init_logger
logger: Incomplete
def get_fake_args_from_graph(graph: fx.GraphModule) -> list[Any]: ...
def create_concrete_args(graph: fx.GraphModule, size: int) -> list[Any]: ...
@dataclasses.dataclass
class RangeEntry:
compile_range: Range
compiled: bool = ...
runnable: Callable[..., Any] = ...
class PiecewiseBackend:
graph: Incomplete
vllm_config: Incomplete
compilation_config: Incomplete
piecewise_compile_index: Incomplete
total_piecewise_compiles: Incomplete
vllm_backend: Incomplete
compiled_runnables: Incomplete
submod_name: Incomplete
is_first_graph: Incomplete
is_last_graph: Incomplete
is_full_graph: Incomplete
is_encoder_compilation: Incomplete
compile_ranges: Incomplete
compile_sizes: Incomplete
sym_shape_indices: Incomplete
returns_tuple: Incomplete
range_entries: dict[Range, RangeEntry]
def __init__(
self,
graph: fx.GraphModule | None,
vllm_config: VllmConfig,
piecewise_compile_index: int,
total_piecewise_compiles: int,
sym_shape_indices: list[int],
vllm_backend: VllmBackend,
returns_tuple: bool,
compiled_runnables: dict[str, Callable[..., Any]] | None = None,
submod_name: str = "",
) -> None: ...
def get_compiled_graph_wrapper(
self, compiled_graph: Callable[..., Any]
) -> Callable[..., Any]: ...
def to_bytes(self) -> dict[str, bytes]: ...
def compile_all_ranges(self) -> None: ...
def load_all_ranges(self) -> None: ...
def __call__(self, *args: Any) -> Any: ...
@@ -0,0 +1,38 @@
import abc
import torch
from _typeshed import Incomplete
from abc import abstractmethod
from collections.abc import Callable as Callable
from types import CodeType
from typing import Any, ParamSpec, TypeVar
from vllm.config import (
CUDAGraphMode as CUDAGraphMode,
CompilationMode as CompilationMode,
get_current_vllm_config as get_current_vllm_config,
)
from vllm.config.compilation import DynamicShapesType as DynamicShapesType
from vllm.logger import init_logger as init_logger
from vllm.utils.nvtx_pytorch_hooks import (
layerwise_nvtx_marker_context as layerwise_nvtx_marker_context,
)
logger: Incomplete
R = TypeVar("R")
P = ParamSpec("P")
class TorchCompileWithNoGuardsWrapper(metaclass=abc.ABCMeta):
def check_invariants_and_forward(self, *args: Any, **kwargs: Any) -> Any: ...
compiled: bool
vllm_config: Incomplete
layerwise_nvtx_tracing_enabled: Incomplete
first_compile: bool
evaluate_guards: Incomplete
def __init__(self) -> None: ...
def aot_compile(self, *args: Any, **kwargs: Any) -> Any: ...
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
@abstractmethod
def forward(self, *args: Any, **kwargs: Any) -> Any: ...
def original_code_object(self) -> CodeType: ...
def bytecode_hook(self, old_code: CodeType, new_code: CodeType) -> None: ...
def reset_compile_wrapper(model: torch.nn.Module) -> None: ...
+107
View File
@@ -0,0 +1,107 @@
from vllm.config.attention import AttentionConfig as AttentionConfig
from vllm.config.cache import CacheConfig as CacheConfig
from vllm.config.compilation import (
CUDAGraphMode as CUDAGraphMode,
CompilationConfig as CompilationConfig,
CompilationMode as CompilationMode,
PassConfig as PassConfig,
)
from vllm.config.device import DeviceConfig as DeviceConfig
from vllm.config.ec_transfer import ECTransferConfig as ECTransferConfig
from vllm.config.kernel import KernelConfig as KernelConfig
from vllm.config.kv_events import KVEventsConfig as KVEventsConfig
from vllm.config.kv_transfer import KVTransferConfig as KVTransferConfig
from vllm.config.load import LoadConfig as LoadConfig
from vllm.config.lora import LoRAConfig as LoRAConfig
from vllm.config.model import (
ModelConfig as ModelConfig,
iter_architecture_defaults as iter_architecture_defaults,
str_dtype_to_torch_dtype as str_dtype_to_torch_dtype,
try_match_architecture_defaults as try_match_architecture_defaults,
)
from vllm.config.multimodal import MultiModalConfig as MultiModalConfig
from vllm.config.observability import ObservabilityConfig as ObservabilityConfig
from vllm.config.offload import (
OffloadBackend as OffloadBackend,
OffloadConfig as OffloadConfig,
PrefetchOffloadConfig as PrefetchOffloadConfig,
UVAOffloadConfig as UVAOffloadConfig,
)
from vllm.config.parallel import (
EPLBConfig as EPLBConfig,
ParallelConfig as ParallelConfig,
)
from vllm.config.pooler import PoolerConfig as PoolerConfig
from vllm.config.profiler import ProfilerConfig as ProfilerConfig
from vllm.config.scheduler import SchedulerConfig as SchedulerConfig
from vllm.config.speculative import SpeculativeConfig as SpeculativeConfig
from vllm.config.speech_to_text import SpeechToTextConfig as SpeechToTextConfig
from vllm.config.structured_outputs import (
StructuredOutputsConfig as StructuredOutputsConfig,
)
from vllm.config.utils import (
ConfigType as ConfigType,
SupportsMetricsInfo as SupportsMetricsInfo,
config as config,
get_attr_docs as get_attr_docs,
is_init_field as is_init_field,
replace as replace,
update_config as update_config,
)
from vllm.config.vllm import (
VllmConfig as VllmConfig,
get_cached_compilation_config as get_cached_compilation_config,
get_current_vllm_config as get_current_vllm_config,
get_current_vllm_config_or_none as get_current_vllm_config_or_none,
get_layers_from_vllm_config as get_layers_from_vllm_config,
set_current_vllm_config as set_current_vllm_config,
)
from vllm.config.weight_transfer import WeightTransferConfig as WeightTransferConfig
__all__ = [
"AttentionConfig",
"CacheConfig",
"CompilationConfig",
"CompilationMode",
"CUDAGraphMode",
"PassConfig",
"DeviceConfig",
"ECTransferConfig",
"KernelConfig",
"KVEventsConfig",
"KVTransferConfig",
"LoadConfig",
"LoRAConfig",
"ModelConfig",
"iter_architecture_defaults",
"str_dtype_to_torch_dtype",
"try_match_architecture_defaults",
"MultiModalConfig",
"ObservabilityConfig",
"OffloadBackend",
"OffloadConfig",
"PrefetchOffloadConfig",
"UVAOffloadConfig",
"EPLBConfig",
"ParallelConfig",
"PoolerConfig",
"SchedulerConfig",
"SpeculativeConfig",
"SpeechToTextConfig",
"StructuredOutputsConfig",
"ProfilerConfig",
"ConfigType",
"SupportsMetricsInfo",
"config",
"get_attr_docs",
"is_init_field",
"replace",
"update_config",
"VllmConfig",
"get_cached_compilation_config",
"get_current_vllm_config",
"get_current_vllm_config_or_none",
"set_current_vllm_config",
"get_layers_from_vllm_config",
"WeightTransferConfig",
]
+21
View File
@@ -0,0 +1,21 @@
from typing import Any, Literal
from vllm.config.utils import config as config
from vllm.v1.attention.backends.registry import (
AttentionBackendEnum as AttentionBackendEnum,
)
@config
class AttentionConfig:
backend: AttentionBackendEnum | None = ...
flash_attn_version: Literal[2, 3, 4] | None = ...
use_prefill_decode_attention: bool = ...
flash_attn_max_num_splits_for_cuda_graph: int = ...
use_cudnn_prefill: bool = ...
use_trtllm_ragged_deepseek_prefill: bool = ...
use_trtllm_attention: bool | None = ...
disable_flashinfer_prefill: bool = ...
disable_flashinfer_q_quantization: bool = ...
use_prefill_query_quantization: bool = ...
def compute_hash(self) -> str: ...
@classmethod
def validate_backend_before(cls, value: Any) -> Any: ...
+40
View File
@@ -0,0 +1,40 @@
from _typeshed import Incomplete
from pydantic import SkipValidation as SkipValidation
from typing import ClassVar
from vllm.config.utils import config as config
from vllm.logger import init_logger as init_logger
logger: Incomplete
CacheDType: Incomplete
MambaDType: Incomplete
MambaCacheMode: Incomplete
PrefixCachingHashAlgo: Incomplete
KVOffloadingBackend: Incomplete
@config
class CacheConfig:
DEFAULT_BLOCK_SIZE: ClassVar[int] = ...
block_size: SkipValidation[int] = ...
user_specified_block_size: bool = ...
gpu_memory_utilization: float = ...
cache_dtype: CacheDType = ...
is_attention_free: bool = ...
num_gpu_blocks_override: int | None = ...
sliding_window: int | None = ...
enable_prefix_caching: bool = ...
prefix_caching_hash_algo: PrefixCachingHashAlgo = ...
calculate_kv_scales: bool = ...
cpu_kvcache_space_bytes: int | None = ...
mamba_page_size_padded: int | None = ...
mamba_block_size: int | None = ...
mamba_cache_dtype: MambaDType = ...
mamba_ssm_cache_dtype: MambaDType = ...
mamba_cache_mode: MambaCacheMode = ...
num_gpu_blocks: int | None = ...
num_cpu_blocks: int | None = ...
kv_sharing_fast_prefill: bool = ...
kv_cache_memory_bytes: int | None = ...
kv_offloading_size: float | None = ...
kv_offloading_backend: KVOffloadingBackend = ...
def compute_hash(self) -> str: ...
def metrics_info(self): ...
+142
View File
@@ -0,0 +1,142 @@
import enum
from _typeshed import Incomplete
from collections import Counter
from collections.abc import Callable as Callable
from pathlib import Path
from typing import Any, Literal
from vllm.compilation.passes.inductor_pass import (
CallableInductorPass as CallableInductorPass,
InductorPass as InductorPass,
)
from vllm.config import VllmConfig as VllmConfig
from vllm.config.utils import (
Range as Range,
config as config,
get_hash_factors as get_hash_factors,
hash_factors as hash_factors,
)
from vllm.logger import init_logger as init_logger
from vllm.platforms import current_platform as current_platform
from vllm.utils.import_utils import resolve_obj_by_qualname as resolve_obj_by_qualname
from vllm.utils.math_utils import round_up as round_up
from vllm.utils.torch_utils import is_torch_equal_or_newer as is_torch_equal_or_newer
logger: Incomplete
class CompilationMode(enum.IntEnum):
NONE = 0
STOCK_TORCH_COMPILE = 1
DYNAMO_TRACE_ONCE = 2
VLLM_COMPILE = 3
class CUDAGraphMode(enum.Enum):
NONE = 0
PIECEWISE = 1
FULL = 2
FULL_DECODE_ONLY = ...
FULL_AND_PIECEWISE = ...
def decode_mode(self) -> CUDAGraphMode: ...
def mixed_mode(self) -> CUDAGraphMode: ...
def has_mode(self, mode: CUDAGraphMode) -> bool: ...
def requires_piecewise_compilation(self) -> bool: ...
def max_cudagraph_mode(self) -> CUDAGraphMode: ...
def has_full_cudagraphs(self) -> bool: ...
def has_piecewise_cudagraphs(self) -> bool: ...
def separate_routine(self) -> bool: ...
@classmethod
def valid_runtime_modes(cls) -> frozenset["CUDAGraphMode"]: ...
def is_valid_runtime_mode(self) -> bool: ...
def __bool__(self) -> bool: ...
@config
class PassConfig:
fuse_norm_quant: bool = ...
fuse_act_quant: bool = ...
fuse_attn_quant: bool = ...
eliminate_noops: bool = ...
enable_sp: bool = ...
fuse_gemm_comms: bool = ...
fuse_allreduce_rms: bool = ...
enable_qk_norm_rope_fusion: bool = ...
fuse_act_padding: bool = ...
fuse_rope_kvcache: bool = ...
rope_kvcache_fusion_max_token_num: int = ...
fi_allreduce_fusion_max_size_mb: float | None = ...
sp_min_token_num: int | None = ...
def flashinfer_max_size(self, world_size: int) -> int | None: ...
@staticmethod
def default_fi_allreduce_fusion_max_size_mb() -> dict[int, float]: ...
def compute_hash(self) -> str: ...
def __post_init__(self) -> None: ...
def log_enabled_passes(self) -> None: ...
class DynamicShapesType(str, enum.Enum):
BACKED = "backed"
UNBACKED = "unbacked"
BACKED_SIZE_OBLIVIOUS = "backed_size_oblivious"
@config
class DynamicShapesConfig:
type: DynamicShapesType = ...
evaluate_guards: bool = ...
assume_32_bit_indexing: bool = ...
def compute_hash(self) -> str: ...
@config
class CompilationConfig:
mode: CompilationMode = ...
debug_dump_path: Path | None = ...
cache_dir: str = ...
compile_cache_save_format: Literal["binary", "unpacked"] = ...
backend: str = ...
custom_ops: list[str] = ...
splitting_ops: list[str] | None = ...
compile_mm_encoder: bool = ...
compile_sizes: list[int | str] | None = ...
compile_ranges_endpoints: list[int] | None = ...
inductor_compile_config: dict = ...
inductor_passes: dict[str, str] = ...
cudagraph_mode: CUDAGraphMode = ...
cudagraph_num_of_warmups: int = ...
cudagraph_capture_sizes: list[int] | None = ...
cudagraph_copy_inputs: bool = ...
cudagraph_specialize_lora: bool = ...
use_inductor_graph_partition: bool = ...
pass_config: PassConfig = ...
max_cudagraph_capture_size: int = ...
dynamic_shapes_config: DynamicShapesConfig = ...
local_cache_dir: str = ...
fast_moe_cold_start: bool | None = ...
enabled_custom_ops: Counter[str] = ...
disabled_custom_ops: Counter[str] = ...
traced_files: set[str] = ...
compilation_time: float = ...
static_forward_context: dict[str, Any] = ...
static_all_moe_layers: list[str] = ...
def compute_hash(self) -> str: ...
@classmethod
def validate_mode_before(cls, value: Any) -> Any: ...
@classmethod
def validate_cudagraph_mode_before(cls, value: Any) -> Any: ...
@classmethod
def validate_pass_config_before(cls, value: Any) -> Any: ...
@classmethod
def validate_compile_cache_save_format(cls, value: str) -> str: ...
def __post_init__(self) -> None: ...
def init_backend(self, vllm_config: VllmConfig) -> str | Callable: ...
def post_init_cudagraph_sizes(self) -> None: ...
def set_splitting_ops_for_v1(
self, all2all_backend: str, data_parallel_size: int = 1
): ...
def set_splitting_ops_for_attn_fusion(self) -> None: ...
def splitting_ops_contain_attention(self) -> bool: ...
def is_attention_compiled_piecewise(self) -> bool: ...
def custom_op_log_check(self) -> None: ...
def is_custom_op_enabled(self, op: str) -> bool: ...
def adjust_cudagraph_sizes_for_spec_decode(
self, uniform_decode_query_len: int, tensor_parallel_size: int
): ...
def adjust_cudagraph_sizes_for_mamba_cache(
self, num_mamba_cache_blocks: int
) -> None: ...
def get_compile_ranges(self) -> list[Range]: ...
+14
View File
@@ -0,0 +1,14 @@
import torch
from _typeshed import Incomplete
from pydantic import ConfigDict, SkipValidation as SkipValidation
from vllm.config.utils import config as config
from vllm.utils.hashing import safe_hash as safe_hash
Device: Incomplete
@config(config=ConfigDict(arbitrary_types_allowed=True))
class DeviceConfig:
device: SkipValidation[Device | torch.device | None] = ...
device_type: str = ...
def compute_hash(self) -> str: ...
def __post_init__(self) -> None: ...
+30
View File
@@ -0,0 +1,30 @@
from _typeshed import Incomplete
from typing import Any, Literal
from vllm.config.utils import config as config
ECProducer: Incomplete
ECConsumer: Incomplete
ECRole = Literal[ECProducer, ECConsumer]
@config
class ECTransferConfig:
ec_connector: str | None = ...
engine_id: str | None = ...
ec_buffer_device: str | None = ...
ec_buffer_size: float = ...
ec_role: ECRole | None = ...
ec_rank: int | None = ...
ec_parallel_size: int = ...
ec_ip: str = ...
ec_port: int = ...
ec_connector_extra_config: dict[str, Any] = ...
ec_connector_module_path: str | None = ...
def compute_hash(self) -> str: ...
def __post_init__(self) -> None: ...
@property
def is_ec_transfer_instance(self) -> bool: ...
@property
def is_ec_producer(self) -> bool: ...
@property
def is_ec_consumer(self) -> bool: ...
def get_from_extra_config(self, key, default) -> Any: ...
+12
View File
@@ -0,0 +1,12 @@
from _typeshed import Incomplete
from collections.abc import Callable as Callable
from vllm.config.utils import config as config
from vllm.utils.hashing import safe_hash as safe_hash
MoEBackend: Incomplete
@config
class KernelConfig:
enable_flashinfer_autotune: bool = ...
moe_backend: MoEBackend = ...
def compute_hash(self) -> str: ...
+14
View File
@@ -0,0 +1,14 @@
from typing import Literal
from vllm.config.utils import config as config
@config
class KVEventsConfig:
enable_kv_cache_events: bool = ...
publisher: Literal["null", "zmq"] = ...
endpoint: str = ...
replay_endpoint: str | None = ...
buffer_steps: int = ...
hwm: int = ...
max_queue_size: int = ...
topic: str = ...
def __post_init__(self) -> None: ...
+33
View File
@@ -0,0 +1,33 @@
from _typeshed import Incomplete
from typing import Any, Literal
from vllm.config.utils import config as config
from vllm.utils.hashing import safe_hash as safe_hash
KVProducer: Incomplete
KVConsumer: Incomplete
KVRole = Literal[KVProducer, KVConsumer]
@config
class KVTransferConfig:
kv_connector: str | None = ...
engine_id: str | None = ...
kv_buffer_device: str | None = ...
kv_buffer_size: float = ...
kv_role: KVRole | None = ...
kv_rank: int | None = ...
kv_parallel_size: int = ...
kv_ip: str = ...
kv_port: int = ...
kv_connector_extra_config: dict[str, Any] = ...
kv_connector_module_path: str | None = ...
enable_permute_local_kv: bool = ...
kv_load_failure_policy: Literal["recompute", "fail"] = ...
def compute_hash(self) -> str: ...
def __post_init__(self) -> None: ...
@property
def is_kv_transfer_instance(self) -> bool: ...
@property
def is_kv_producer(self) -> bool: ...
@property
def is_kv_consumer(self) -> bool: ...
def get_from_extra_config(self, key, default) -> Any: ...
+22
View File
@@ -0,0 +1,22 @@
from _typeshed import Incomplete
from vllm.config.utils import config as config
from vllm.logger import init_logger as init_logger
from vllm.model_executor.model_loader import LoadFormats as LoadFormats
from vllm.model_executor.model_loader.tensorizer import (
TensorizerConfig as TensorizerConfig,
)
from vllm.utils.hashing import safe_hash as safe_hash
logger: Incomplete
@config
class LoadConfig:
load_format: str | LoadFormats = ...
download_dir: str | None = ...
safetensors_load_strategy: str = ...
model_loader_extra_config: dict | TensorizerConfig = ...
device: str | None = ...
ignore_patterns: list[str] | str = ...
use_tqdm_on_load: bool = ...
pt_load_map_location: str | dict[str, str] = ...
def compute_hash(self) -> str: ...
+26
View File
@@ -0,0 +1,26 @@
import torch
from _typeshed import Incomplete
from pydantic import ConfigDict
from vllm.config import ModelConfig as ModelConfig
from vllm.config.cache import CacheConfig as CacheConfig
from vllm.config.utils import config as config
from vllm.logger import init_logger as init_logger
from vllm.utils.hashing import safe_hash as safe_hash
logger: Incomplete
LoRADType: Incomplete
MaxLoRARanks: Incomplete
LoRAExtraVocabSize: Incomplete
@config(config=ConfigDict(arbitrary_types_allowed=True))
class LoRAConfig:
max_lora_rank: MaxLoRARanks = ...
max_loras: int = ...
fully_sharded_loras: bool = ...
max_cpu_loras: int | None = ...
lora_dtype: torch.dtype | LoRADType = ...
default_mm_loras: dict[str, str] | None = ...
enable_tower_connector_lora: bool = ...
specialize_active_lora: bool = ...
def compute_hash(self) -> str: ...
def verify_with_model_config(self, model_config: ModelConfig): ...
+260
View File
@@ -0,0 +1,260 @@
import torch
from _typeshed import Incomplete
from collections.abc import Callable, Generator
from dataclasses import InitVar
from functools import cached_property as cached_property
from pydantic import ConfigDict
from transformers import PretrainedConfig
from typing import Any
from vllm.config.load import LoadConfig as LoadConfig
from vllm.config.model_arch import ModelArchitectureConfig as ModelArchitectureConfig
from vllm.config.multimodal import (
MMCacheType as MMCacheType,
MMEncoderTPMode as MMEncoderTPMode,
MultiModalConfig as MultiModalConfig,
)
from vllm.config.parallel import ParallelConfig as ParallelConfig
from vllm.config.pooler import PoolerConfig as PoolerConfig
from vllm.config.scheduler import RunnerType as RunnerType
from vllm.config.utils import config as config, getattr_iter as getattr_iter
from vllm.logger import init_logger as init_logger
from vllm.model_executor.layers.quantization import (
QuantizationMethods as QuantizationMethods,
)
from vllm.platforms import current_platform as current_platform
from vllm.tasks import ScoreType as ScoreType
from vllm.transformers_utils.config import (
ConfigFormat as ConfigFormat,
get_config as get_config,
get_hf_image_processor_config as get_hf_image_processor_config,
get_hf_text_config as get_hf_text_config,
get_pooling_config as get_pooling_config,
get_sentence_transformer_tokenizer_config as get_sentence_transformer_tokenizer_config,
is_encoder_decoder as is_encoder_decoder,
is_rope_parameters_nested as is_rope_parameters_nested,
try_get_dense_modules as try_get_dense_modules,
try_get_generation_config as try_get_generation_config,
try_get_tokenizer_config as try_get_tokenizer_config,
uses_mrope as uses_mrope,
uses_xdrope_dim as uses_xdrope_dim,
)
from vllm.transformers_utils.gguf_utils import (
is_gguf as is_gguf,
is_remote_gguf as is_remote_gguf,
maybe_patch_hf_config_from_gguf as maybe_patch_hf_config_from_gguf,
split_remote_gguf as split_remote_gguf,
)
from vllm.transformers_utils.model_arch_config_convertor import (
MODEL_ARCH_CONFIG_CONVERTORS as MODEL_ARCH_CONFIG_CONVERTORS,
ModelArchConfigConvertorBase as ModelArchConfigConvertorBase,
)
from vllm.transformers_utils.runai_utils import (
ObjectStorageModel as ObjectStorageModel,
is_runai_obj_uri as is_runai_obj_uri,
)
from vllm.transformers_utils.utils import maybe_model_redirect as maybe_model_redirect
from vllm.utils.import_utils import LazyLoader as LazyLoader
from vllm.v1.attention.backends.registry import (
AttentionBackendEnum as AttentionBackendEnum,
)
from vllm.v1.sample.logits_processor import LogitsProcessor as LogitsProcessor
logger: Incomplete
RunnerOption: Incomplete
ConvertType: Incomplete
ConvertOption: Incomplete
TokenizerMode: Incomplete
ModelDType: Incomplete
LogprobsMode: Incomplete
HfOverrides = dict[str, Any] | Callable[[PretrainedConfig], PretrainedConfig]
ModelImpl: Incomplete
LayerBlockType: Incomplete
AttnTypeStr: Incomplete
@config(config=ConfigDict(arbitrary_types_allowed=True))
class ModelConfig:
model: str = ...
model_weights: str = ...
runner: RunnerOption = ...
convert: ConvertOption = ...
tokenizer: str = ...
tokenizer_mode: TokenizerMode | str = ...
trust_remote_code: bool = ...
dtype: ModelDType | torch.dtype = ...
seed: int = ...
hf_config: PretrainedConfig = ...
hf_text_config: PretrainedConfig = ...
hf_config_path: str | None = ...
allowed_local_media_path: str = ...
allowed_media_domains: list[str] | None = ...
revision: str | None = ...
code_revision: str | None = ...
tokenizer_revision: str | None = ...
max_model_len: int = ...
spec_target_max_model_len: int | None = ...
quantization: QuantizationMethods | str | None = ...
allow_deprecated_quantization: bool = ...
enforce_eager: bool = ...
enable_return_routed_experts: bool = ...
max_logprobs: int = ...
logprobs_mode: LogprobsMode = ...
disable_sliding_window: bool = ...
disable_cascade_attn: bool = ...
skip_tokenizer_init: bool = ...
enable_prompt_embeds: bool = ...
served_model_name: str | list[str] | None = ...
config_format: str | ConfigFormat = ...
hf_token: bool | str | None = ...
hf_overrides: HfOverrides = ...
generation_config: str = ...
override_generation_config: dict[str, Any] = ...
enable_sleep_mode: bool = ...
model_impl: str | ModelImpl = ...
override_attention_dtype: str | None = ...
logits_processors: list[str | type[LogitsProcessor]] | None = ...
io_processor_plugin: str | None = ...
pooler_config: PoolerConfig | None = ...
multimodal_config: MultiModalConfig | None = ...
language_model_only: InitVar[bool] = ...
limit_mm_per_prompt: InitVar[dict[str, int | dict[str, int]] | None] = ...
enable_mm_embeds: InitVar[bool | None] = ...
media_io_kwargs: InitVar[dict[str, dict[str, Any]] | None] = ...
mm_processor_kwargs: InitVar[dict[str, Any] | None] = ...
mm_processor_cache_gb: InitVar[float | None] = ...
mm_processor_cache_type: InitVar[MMCacheType | None] = ...
mm_shm_cache_max_object_size_mb: InitVar[int | None] = ...
mm_encoder_only: InitVar[bool | None] = ...
mm_encoder_tp_mode: InitVar[MMEncoderTPMode | None] = ...
mm_encoder_attn_backend: InitVar[AttentionBackendEnum | str | None] = ...
interleave_mm_strings: InitVar[bool | None] = ...
skip_mm_profiling: InitVar[bool | None] = ...
video_pruning_rate: InitVar[float | None] = ...
def compute_hash(self) -> str: ...
attention_chunk_size = ...
encoder_config = ...
hf_image_processor_config = ...
model_arch_config = ...
runner_type = ...
convert_type = ...
original_max_model_len = ...
config_updated = ...
def __post_init__(
self,
language_model_only: bool,
limit_mm_per_prompt: dict[str, int | dict[str, int]] | None,
enable_mm_embeds: bool | None,
media_io_kwargs: dict[str, dict[str, Any]] | None,
mm_processor_kwargs: dict[str, Any] | None,
mm_processor_cache_gb: float | None,
mm_processor_cache_type: MMCacheType | None,
mm_shm_cache_max_object_size_mb: int | None,
mm_encoder_only: bool | None,
mm_encoder_tp_mode: MMEncoderTPMode | None,
mm_encoder_attn_backend: AttentionBackendEnum | str | None,
interleave_mm_strings: bool | None,
skip_mm_profiling: bool | None,
video_pruning_rate: float | None,
) -> None: ...
def get_model_arch_config(self) -> ModelArchitectureConfig: ...
@classmethod
def validate_quantization_before(cls, value: Any) -> Any: ...
def validate_model_config_after(self) -> ModelConfig: ...
def using_transformers_backend(self) -> bool: ...
@property
def registry(self): ...
@property
def architectures(self) -> list[str]: ...
@property
def architecture(self) -> str: ...
def maybe_pull_model_tokenizer_for_runai(
self, model: str, tokenizer: str
) -> None: ...
def verify_dual_chunk_attention_config(self, load_config: LoadConfig) -> None: ...
def verify_with_parallel_config(self, parallel_config: ParallelConfig) -> None: ...
def get_sliding_window(self) -> int | None: ...
def get_vocab_size(self) -> int: ...
def get_hidden_size(self) -> int: ...
def get_inputs_embeds_size(self) -> int: ...
@property
def is_deepseek_mla(self) -> bool: ...
@cached_property
def is_mm_prefix_lm(self) -> bool: ...
def get_head_size(self) -> int: ...
def get_total_num_kv_heads(self) -> int: ...
def get_num_kv_heads(self, parallel_config: ParallelConfig) -> int: ...
def get_num_attention_heads(self, parallel_config: ParallelConfig) -> int: ...
def get_num_experts(self) -> int: ...
def get_total_num_hidden_layers(self) -> int: ...
def get_layers_start_end_indices(
self, parallel_config: ParallelConfig
) -> tuple[int, int]: ...
def get_num_layers(self, parallel_config: ParallelConfig) -> int: ...
def get_num_layers_by_block_type(
self, parallel_config: ParallelConfig, block_type: LayerBlockType = "attention"
) -> int: ...
def get_mamba_chunk_size(self) -> int | None: ...
def get_multimodal_config(self) -> MultiModalConfig: ...
def try_get_generation_config(self) -> dict[str, Any]: ...
def get_diff_sampling_param(self) -> dict[str, Any]: ...
@cached_property
def is_encoder_decoder(self) -> bool: ...
@property
def uses_alibi(self) -> bool: ...
@property
def uses_mrope(self) -> bool: ...
@property
def uses_xdrope_dim(self) -> int: ...
@property
def is_multimodal_model(self) -> bool: ...
@property
def is_multimodal_raw_input_only_model(self) -> bool: ...
@property
def requires_raw_input_tokens(self) -> bool: ...
@property
def score_type(self) -> ScoreType: ...
@property
def is_pp_supported(self) -> bool: ...
@property
def is_attention_free(self) -> bool: ...
@property
def is_hybrid(self) -> bool: ...
@property
def has_noops(self) -> bool: ...
@property
def has_inner_state(self): ...
@property
def supports_mamba_prefix_caching(self) -> bool: ...
@property
def use_mla(self) -> bool: ...
@property
def is_matryoshka(self) -> bool: ...
@property
def matryoshka_dimensions(self): ...
@property
def use_sep_token(self) -> bool: ...
@property
def head_dtype(self) -> torch.dtype: ...
@property
def embedding_size(self): ...
def get_and_verify_max_len(self, max_model_len: int): ...
@property
def attn_type(self) -> AttnTypeStr: ...
@property
def is_chunked_prefill_supported(self) -> bool: ...
@property
def is_prefix_caching_supported(self) -> bool: ...
@property
def is_moe(self) -> bool: ...
@property
def is_quantized(self) -> bool: ...
def is_nvfp4_quantized(self) -> bool: ...
def get_served_model_name(model: str, served_model_name: str | list[str] | None): ...
def iter_architecture_defaults() -> Generator[Incomplete, Incomplete]: ...
def try_match_architecture_defaults(
architecture: str,
*,
runner_type: RunnerType | None = None,
convert_type: ConvertType | None = None,
) -> tuple[str, tuple[RunnerType, ConvertType]] | None: ...
def str_dtype_to_torch_dtype(type: str): ...
+20
View File
@@ -0,0 +1,20 @@
from _typeshed import Incomplete
from typing import Any
from vllm.logger import init_logger as init_logger
logger: Incomplete
class ModelArchitectureConfig:
architectures: list[str] | None
model_type: str
text_model_type: str | None
hidden_size: int
total_num_hidden_layers: int
total_num_attention_heads: int
head_size: int
vocab_size: int
total_num_kv_heads: int
num_experts: int
quantization_config: dict[str, Any] | None
is_deepseek_mla: bool
derived_max_model_len_and_key: tuple[float, str | None]
+55
View File
@@ -0,0 +1,55 @@
from _typeshed import Incomplete
from collections.abc import Mapping
from typing import Any, TypeAlias, TypedDict
from vllm.config.utils import config as config
from vllm.utils.hashing import safe_hash as safe_hash
from vllm.v1.attention.backends.registry import (
AttentionBackendEnum as AttentionBackendEnum,
)
class BaseDummyOptions:
count: int
class VideoDummyOptions(BaseDummyOptions):
num_frames: int | None
width: int | None
height: int | None
class ImageDummyOptions(BaseDummyOptions):
width: int | None
height: int | None
class AudioDummyOptions(BaseDummyOptions):
length: int | None
class MultiModalDummyOptionsBuiltins(TypedDict, total=False):
image: ImageDummyOptions
video: VideoDummyOptions
audio: AudioDummyOptions
MMEncoderTPMode: Incomplete
MMCacheType: Incomplete
MMDummyOptions: TypeAlias = dict[str, BaseDummyOptions]
@config
class MultiModalConfig:
language_model_only: bool = ...
limit_per_prompt: MMDummyOptions = ...
enable_mm_embeds: bool = ...
media_io_kwargs: dict[str, dict[str, Any]] = ...
mm_processor_kwargs: dict[str, object] | None = ...
mm_processor_cache_gb: float = ...
mm_processor_cache_type: MMCacheType = ...
mm_shm_cache_max_object_size_mb: int = ...
mm_encoder_only: bool = ...
mm_encoder_tp_mode: MMEncoderTPMode = ...
mm_encoder_attn_backend: AttentionBackendEnum | None = ...
interleave_mm_strings: bool = ...
skip_mm_profiling: bool = ...
video_pruning_rate: float | None = ...
def compute_hash(self) -> str: ...
def get_limit_per_prompt(self, modality: str) -> int: ...
def merge_mm_processor_kwargs(
self, inference_kwargs: Mapping[str, object]
) -> dict[str, object]: ...
def is_multimodal_pruning_enabled(self): ...
@@ -0,0 +1,27 @@
from _typeshed import Incomplete
from functools import cached_property as cached_property
from vllm import version as version
from vllm.config.utils import config as config
from vllm.utils.hashing import safe_hash as safe_hash
DetailedTraceModules: Incomplete
@config
class ObservabilityConfig:
show_hidden_metrics_for_version: str | None = ...
@cached_property
def show_hidden_metrics(self) -> bool: ...
otlp_traces_endpoint: str | None = ...
collect_detailed_traces: list[DetailedTraceModules] | None = ...
kv_cache_metrics: bool = ...
kv_cache_metrics_sample: float = ...
cudagraph_metrics: bool = ...
enable_layerwise_nvtx_tracing: bool = ...
enable_mfu_metrics: bool = ...
enable_mm_processor_stats: bool = ...
enable_logging_iteration_details: bool = ...
@cached_property
def collect_model_forward_time(self) -> bool: ...
@cached_property
def collect_model_execute_time(self) -> bool: ...
def compute_hash(self) -> str: ...
+24
View File
@@ -0,0 +1,24 @@
from _typeshed import Incomplete
from vllm.config.utils import config as config
OffloadBackend: Incomplete
@config
class UVAOffloadConfig:
cpu_offload_gb: float = ...
cpu_offload_params: set[str] = ...
@config
class PrefetchOffloadConfig:
offload_group_size: int = ...
offload_num_in_group: int = ...
offload_prefetch_step: int = ...
offload_params: set[str] = ...
@config
class OffloadConfig:
offload_backend: OffloadBackend = ...
uva: UVAOffloadConfig = ...
prefetch: PrefetchOffloadConfig = ...
def validate_offload_config(self) -> OffloadConfig: ...
def compute_hash(self) -> str: ...
+126
View File
@@ -0,0 +1,126 @@
from _typeshed import Incomplete
from collections.abc import Callable as Callable
from ray.runtime_env import RuntimeEnv as RuntimeEnv
from ray.util.placement_group import PlacementGroup as PlacementGroup
from torch.distributed import ProcessGroup as ProcessGroup, Store as Store
from typing import Literal, overload
from vllm.config.utils import config as config
from vllm.logger import init_logger as init_logger
from vllm.model_executor.layers.batch_invariant import (
vllm_is_batch_invariant as vllm_is_batch_invariant,
)
from vllm.platforms import current_platform as current_platform
from vllm.utils.network_utils import get_open_ports_list as get_open_ports_list
from vllm.utils.torch_utils import (
cuda_device_count_stateless as cuda_device_count_stateless,
)
from vllm.v1.executor import Executor as Executor
logger: Incomplete
ExpertPlacementStrategy: Incomplete
DistributedExecutorBackend: Incomplete
DataParallelBackend: Incomplete
EPLBPolicyOption: Incomplete
DCPCommBackend: Incomplete
All2AllBackend: Incomplete
@config
class EPLBConfig:
window_size: int = ...
step_interval: int = ...
num_redundant_experts: int = ...
log_balancedness: bool = ...
log_balancedness_interval: int = ...
use_async: bool = ...
policy: EPLBPolicyOption = ...
@config
class ParallelConfig:
pipeline_parallel_size: int = ...
tensor_parallel_size: int = ...
prefill_context_parallel_size: int = ...
data_parallel_size: int = ...
data_parallel_size_local: int = ...
data_parallel_rank: int = ...
data_parallel_rank_local: int | None = ...
data_parallel_master_ip: str = ...
data_parallel_rpc_port: int = ...
data_parallel_master_port: int = ...
data_parallel_backend: DataParallelBackend = ...
data_parallel_external_lb: bool = ...
data_parallel_hybrid_lb: bool = ...
is_moe_model: bool | None = ...
enable_expert_parallel: bool = ...
enable_eplb: bool = ...
eplb_config: EPLBConfig = ...
expert_placement_strategy: ExpertPlacementStrategy = ...
all2all_backend: All2AllBackend = ...
max_parallel_loading_workers: int | None = ...
disable_custom_all_reduce: bool = ...
enable_elastic_ep: bool = ...
enable_dbo: bool = ...
ubatch_size: int = ...
dbo_decode_token_threshold: int = ...
dbo_prefill_token_threshold: int = ...
disable_nccl_for_dp_synchronization: bool | None = ...
ray_workers_use_nsight: bool = ...
ray_runtime_env: RuntimeEnv | None = ...
placement_group: PlacementGroup | None = ...
distributed_executor_backend: (
str | DistributedExecutorBackend | type[Executor] | None
) = ...
worker_cls: str = ...
sd_worker_cls: str = ...
worker_extension_cls: str = ...
master_addr: str = ...
master_port: int = ...
node_rank: int = ...
nnodes: int = ...
distributed_timeout_seconds: int | None = ...
world_size: int = ...
rank: int = ...
decode_context_parallel_size: int = ...
dcp_kv_cache_interleave_size: int = ...
dcp_comm_backend: DCPCommBackend = ...
cp_kv_cache_interleave_size: int = ...
data_parallel_index: int = ...
@property
def world_size_across_dp(self) -> int: ...
@property
def use_ubatching(self) -> bool: ...
@property
def num_ubatches(self) -> int: ...
@property
def local_engines_only(self) -> bool: ...
def get_next_dp_init_port(self) -> int: ...
def allocate_elastic_ep_ports(self) -> None: ...
def get_next_stateless_world_group_port(self) -> list[int]: ...
def get_next_stateless_dp_group_port(self) -> list[int]: ...
def get_next_stateless_ep_group_port(self) -> list[int]: ...
def get_next_stateless_eplb_group_port(self) -> list[int]: ...
@overload
def stateless_init_dp_group(
self, return_store: Literal[False] = ...
) -> ProcessGroup: ...
@overload
def stateless_init_dp_group(
self, return_store: Literal[True] = ...
) -> tuple[ProcessGroup, Store]: ...
@property
def use_sequence_parallel_moe(self) -> bool: ...
@property
def node_rank_within_dp(self) -> int: ...
@property
def nnodes_within_dp(self) -> int: ...
@property
def local_world_size(self) -> int: ...
@staticmethod
def has_unfinished_dp(dp_group: ProcessGroup, has_unfinished: bool) -> bool: ...
@staticmethod
def sync_kv_cache_memory_size(
dp_group: ProcessGroup, kv_cache_memory: int
) -> int: ...
def compute_hash(self): ...
def __post_init__(self) -> None: ...
@property
def use_ray(self) -> bool: ...
+27
View File
@@ -0,0 +1,27 @@
from _typeshed import Incomplete
from vllm.config.utils import config as config
from vllm.logger import init_logger as init_logger
from vllm.utils.hashing import safe_hash as safe_hash
logger: Incomplete
SequencePoolingType: Incomplete
SEQ_POOLING_TYPES: tuple[SequencePoolingType, ...]
TokenPoolingType: Incomplete
TOK_POOLING_TYPES: tuple[TokenPoolingType, ...]
@config
class PoolerConfig:
pooling_type: SequencePoolingType | TokenPoolingType | None = ...
seq_pooling_type: SequencePoolingType | None = ...
tok_pooling_type: TokenPoolingType | None = ...
use_activation: bool | None = ...
dimensions: int | None = ...
enable_chunked_processing: bool = ...
max_embed_len: int | None = ...
logit_bias: float | None = ...
step_tag_id: int | None = ...
returned_token_ids: list[int] | None = ...
def __post_init__(self) -> None: ...
def get_seq_pooling_type(self) -> SequencePoolingType: ...
def get_tok_pooling_type(self) -> TokenPoolingType: ...
def compute_hash(self) -> str: ...
+25
View File
@@ -0,0 +1,25 @@
from _typeshed import Incomplete
from vllm.config.utils import config as config
from vllm.logger import init_logger as init_logger
from vllm.utils.hashing import safe_hash as safe_hash
logger: Incomplete
ProfilerKind: Incomplete
@config
class ProfilerConfig:
profiler: ProfilerKind | None = ...
torch_profiler_dir: str = ...
torch_profiler_with_stack: bool = ...
torch_profiler_with_flops: bool = ...
torch_profiler_use_gzip: bool = ...
torch_profiler_dump_cuda_time_total: bool = ...
torch_profiler_record_shapes: bool = ...
torch_profiler_with_memory: bool = ...
ignore_frontend: bool = ...
delay_iterations: int = ...
max_iterations: int = ...
warmup_iterations: int = ...
active_iterations: int = ...
wait_iterations: int = ...
def compute_hash(self) -> str: ...
+44
View File
@@ -0,0 +1,44 @@
from _typeshed import Incomplete
from collections.abc import Callable as Callable
from dataclasses import InitVar
from typing import ClassVar
from typing_extensions import Self
from vllm.config.utils import config as config
from vllm.logger import init_logger as init_logger
from vllm.utils.hashing import safe_hash as safe_hash
from vllm.utils.import_utils import resolve_obj_by_qualname as resolve_obj_by_qualname
from vllm.v1.core.sched.interface import SchedulerInterface as SchedulerInterface
logger: Incomplete
RunnerType: Incomplete
SchedulerPolicy: Incomplete
@config
class SchedulerConfig:
max_model_len: InitVar[int]
is_encoder_decoder: InitVar[bool]
DEFAULT_MAX_NUM_BATCHED_TOKENS: ClassVar[int] = ...
DEFAULT_MAX_NUM_SEQS: ClassVar[int] = ...
runner_type: RunnerType = ...
max_num_batched_tokens: int = ...
max_num_scheduled_tokens: int | None = ...
max_num_seqs: int = ...
max_num_partial_prefills: int = ...
max_long_partial_prefills: int = ...
long_prefill_token_threshold: int = ...
enable_chunked_prefill: bool = ...
is_multimodal_model: bool = ...
max_num_encoder_input_tokens: int = ...
encoder_cache_size: int = ...
policy: SchedulerPolicy = ...
disable_chunked_mm_input: bool = ...
scheduler_cls: str | type[object] | None = ...
disable_hybrid_kv_cache_manager: bool | None = ...
async_scheduling: bool | None = ...
stream_interval: int = ...
@staticmethod
def default_factory(**kwargs): ...
def get_scheduler_cls(self) -> type["SchedulerInterface"]: ...
def compute_hash(self) -> str: ...
def __post_init__(self, max_model_len: int, is_encoder_decoder: bool) -> None: ...
def verify_max_model_len(self, max_model_len: int) -> Self: ...
+66
View File
@@ -0,0 +1,66 @@
import vllm.model_executor.layers.quantization as me_quant
from _typeshed import Incomplete
from pydantic import SkipValidation as SkipValidation
from transformers import PretrainedConfig as PretrainedConfig
from vllm.config import LoadConfig as LoadConfig
from vllm.config.model import ModelConfig as ModelConfig
from vllm.config.parallel import ParallelConfig as ParallelConfig
from vllm.config.utils import config as config
from vllm.logger import init_logger as init_logger
from vllm.transformers_utils.config import get_hf_text_config as get_hf_text_config
from vllm.utils.hashing import safe_hash as safe_hash
from vllm.utils.import_utils import (
LazyLoader as LazyLoader,
has_arctic_inference as has_arctic_inference,
)
logger: Incomplete
MTPModelTypes: Incomplete
EagleModelTypes: Incomplete
NgramGPUTypes: Incomplete
SpeculativeMethod: Incomplete
@config
class SpeculativeConfig:
enforce_eager: bool | None = ...
num_speculative_tokens: int = ...
model: str | None = ...
method: SpeculativeMethod | None = ...
draft_tensor_parallel_size: int | None = ...
tensor_parallel_size: int | None = ...
quantization: me_quant.QuantizationMethods | None = ...
max_model_len: int | None = ...
revision: str | None = ...
code_revision: str | None = ...
disable_padded_drafter_batch: bool = ...
use_local_argmax_reduction: bool = ...
prompt_lookup_max: int | None = ...
prompt_lookup_min: int | None = ...
speculative_token_tree: str | None = ...
parallel_drafting: bool = ...
target_model_config: SkipValidation[ModelConfig] = ...
target_parallel_config: SkipValidation[ParallelConfig] = ...
draft_model_config: SkipValidation[ModelConfig] = ...
draft_parallel_config: SkipValidation[ParallelConfig] = ...
suffix_decoding_max_tree_depth: int = ...
suffix_decoding_max_cached_requests: int = ...
suffix_decoding_max_spec_factor: float = ...
suffix_decoding_min_token_prob: float = ...
draft_load_config: LoadConfig | None = ...
def compute_hash(self) -> str: ...
@staticmethod
def hf_config_override(hf_config: PretrainedConfig) -> PretrainedConfig: ...
def __post_init__(self): ...
def update_arch_(self) -> None: ...
@staticmethod
def create_draft_parallel_config(
target_parallel_config: ParallelConfig,
speculative_draft_tensor_parallel_size: int,
) -> ParallelConfig: ...
def verify_equal_vocab_size_if_draft_model(self) -> None: ...
@property
def max_num_new_slots_for_drafting(self) -> int: ...
def use_eagle(self) -> bool: ...
def uses_draft_model(self) -> bool: ...
def uses_extract_hidden_states(self) -> bool: ...
def use_ngram_gpu(self) -> bool: ...
@@ -0,0 +1,10 @@
from vllm.config.utils import config as config
@config
class SpeechToTextConfig:
sample_rate: float = ...
max_audio_clip_s: int | None = ...
overlap_chunk_second: int = ...
min_energy_split_window_size: int | None = ...
@property
def allow_audio_chunking(self) -> bool: ...
@@ -0,0 +1,15 @@
from _typeshed import Incomplete
from vllm.config.utils import config as config
from vllm.utils.hashing import safe_hash as safe_hash
StructuredOutputsBackend: Incomplete
@config
class StructuredOutputsConfig:
backend: StructuredOutputsBackend = ...
disable_any_whitespace: bool = ...
disable_additional_properties: bool = ...
reasoning_parser: str = ...
reasoning_parser_plugin: str = ...
enable_in_reasoning: bool = ...
def compute_hash(self) -> str: ...

Some files were not shown because too many files have changed in this diff Show More