update ar_dit_test

This commit is contained in:
hsliu committed 2025-09-28 21:58:48 +08:00
1 parent ff9c7d4536
commit 81f04d3cf9
32 files changed
+1192 -1908

No files matched your search

-1
View File
@@ -238,7 +238,6 @@ Dockerfile.dev
# Kubernetes
k8s/
*.yaml
*.yml
!configs/*.yaml
!configs/*.yml
+110 -378
View File
@@ -2,21 +2,22 @@
## Overview
vLLM-omni is a multi-modality extension for vLLM that supports non-autoregressive structures and non-textual outputs. This document outlines the key software abstractions, APIs, and dependencies for the system, designed to maximize reuse of vLLM's proven architecture.
vLLM-omni extends vLLM with multi-stage, multimodal capabilities. It supports both autoregressive (AR) stages and diffusion (DiT) stages, enabling pipelines such as text→image generation.
## Architecture Principles
1. **vLLM V1 Compatibility**: Built on vLLM's Engine V1 architecture with AsyncLLM and EngineCore patterns
2. **Stage-based Processing**: Models are divided into multiple stages, each processed by different Engine Cores
3. **Multiple Engine Core Support**: Each stage can use either AR Engine Core (reusing vLLM) or Diffusion Engine Core (new DiT support), or other new Cores
4. **Worker Process Pattern**: Follows vLLM's multiprocess worker architecture for scalability
5. **Extensibility**: Easy integration of new modalities, model architectures, and output formats
1. **vLLM V1 Compatibility** – Reuse LLMEngine/AsyncLLM and executor abstractions.
2. **Stage-based Processing** – Each stage encapsulates its own engine and configuration.
3. **Executor/Worker Separation** – Executors remain thin; model-specific logic lives in workers and runners.
4. **Extensibility** – New modalities can be added by implementing dedicated stage configs, workers, and output processors.
## Key Data Flow
API Server --> OmniLLM/AsyncOmniLLM (New, including multi engines) --> LLMEngine/AsyncLLM --> Engine Core
--> Scheduler (New one for DiT) --> Executor (New one for diffusers) --> Worker (New one for DiT)
--> ModelRunner (New one for AR hiddenstate, New one for DiT) --> RequestState --> OutputProcessor (New one for final multimodal output)
```
Request → OmniLLM/AsyncOmniLLM → StageManager → LLMEngine/Executor → Worker → ModelRunner → OutputProcessor → Response
```
For diffusion stages, the executor hands off to `DiffusionGPUWorker`, which wraps `DiffusionModelRunner` (responsible for diffusers pipeline loading and inference).
## Core Components (Ordered by Data Flow)
@@ -27,399 +28,130 @@ pip install vllm-omni
```
### 2. Online inference launch (Entry Point)
Keep consistency with the vllm main branch
```bash
vllm serve Qwen/Qwen2.5-Omni-7B --omni --port 8000
vllm serve Qwen/Qwen3-0.6B --omni --port 8000
```
#### Design Architecture
```mermaid
graph TD
A[vllm serve model --omni] --> B[vLLM-omni CLI Wrapper]
B --> C{Detect --omni flag}
C -->|Yes| D[Parse omniConfig]
C -->|No| E[Forward to vLLM CLI]
D --> F[Initialize AsyncOmniLLM]
F --> G[Start Omni Server]
G --> H[Multi-stage Processing]
E --> I[Standard vLLM Pipeline]
subgraph "vLLM-omni Components"
F
G
H
end
subgraph "vLLM Components"
I
end
```
#### Omni Serve Command Implementation
File: vllm_omni/entrypoints/cli/main.py
### 3. Omni Serve Command (CLI)
```python
import sys
import argparse
from typing import List, Optional
from vllm_omni.entrypoints.omni import OmniServeCommand
def main():
"""Main CLI entry point that intercepts vLLM commands"""
# Check if --omni flag is present
if "--omni" in sys.argv:
# Remove --omni flag and process with vLLM-omni
omni_args = [arg for arg in sys.argv[1:] if arg != "--omni"]
omni_serve = OmniServeCommand()
omni_serve.run(omni_args)
else:
# Forward to original vLLM CLI
from vllm.entrypoints.cli.main import main as vllm_main
vllm_main()
if __name__ == "__main__":
main()
# vllm_omni/entrypoints/cli/main.py
if "--omni" in sys.argv:
omni_args = [arg for arg in sys.argv[1:] if arg not in {"--omni", "serve"}]
OmniServeCommand().run(omni_args)
else:
from vllm.entrypoints.cli.main import main as vllm_main
vllm_main()
```
#### vLLM Plugin Integration
File: vllm_omni/pyproject.toml
### 4. Offline Inference (OmniLLM)
```python
[project.scripts]
# Override vLLM's CLI entry point
vllm = "vllm_omni.cli:main"
# Add entry point for vLLM integration
[project.entry-points."vllm.plugins"]
omni = "vllm_omni.plugin:OmniPlugin"
```
### 3. Offline inference launch (Entry Point)
Design an upper level class to incorporate multi Engines, each engine has a engine call.
```python
from vllm.entrypoints.llm import LLM
from vllm.v1.engine.llm_engine import LLMEngine
# vllm_omni/entrypoints/omni_llm.py
class OmniLLM(LLM):
"""Extended LLM supporting multiple engines and stage-based processing"""
def __init__(self, stage_configs: List[OmniStageConfig]):
super().__init__()
self.stage_configs = stage_configs
self.engine_list = [] # List of AsyncLLM instances for each stage
def __init__(..., stage_configs):
super().__init__(model=stage_configs[0].model_path if stage_configs else "test-model")
self.stage_manager = StageManager(stage_configs)
self.output_processor = MultimodalOutputProcessor()
self._initialize_stage_engines()
def _initialize_stage_engines(self) -> None:
"""Initialize LLMEngine instances for each stage"""
for stage_config in self.stage_configs:
stage_llm = LLMEngine.from_vllm_config(
vllm_config=stage_config.vllm_config,
executor_class=self.executor_class,
log_stats=self.log_stats
)
self.engine_list.append(stage_llm)
def generate(
self,
stage_args_list: List[stage_args],
use_tqdm: Union[bool, Callable[..., tqdm]] = True,
lora_request: Optional[Union[list[LoRARequest], LoRARequest]] = None,
priority: Optional[list[int]] = None,
) -> list[RequestOutput]:
"""Main generation interface - orchestrates multi-stage processing"""
self.stage_manager.initialize_engines()
# Process through each stage sequentially
for i, stage_config in enumerate(self.stage_configs):
stage_engine = self.engine_list[i]
# Prepare input for this stage
stage_args = stage_args_list[i]
prompt_str, engine_request, tokenization_kwargs = self._process_stage_inputs(stage_config, **stage_args)
# Add inputs to Engine
stage_engine.add_request(requesy_id, prompt_str, tokenization_kwargs)
# Run Engine
stage_output = stage_engine.step()
# Update stage input and output of each stage for later usage
self._update_stage_io(stage_output, stage_config)
# Process final output
final_output = self.output_processor.process_output(
self.stage_configs[-1].stage_output
)
return final_output
def _process_stage_inputs(stage_config, **stage_args) -> Any:
"""Prepare input for specific stage"""
if stage_config.engine_type == "AR":
return self._process_ar_inputs(**stage_args)
elif stage_config.engine_type == "DiT":
return self._process_dit_inputs(**stage_args)
else:
raise NotImplementedError
def _process_dit_inputs(**stage_args)-> Any:
image_latent = self.vae.encode(**stage_args)
return image_latent
def generate(...):
current_output = None
for idx, (config, args) in enumerate(zip(self.stage_configs, stage_args_list)):
engine = self.stage_manager.get_engine(idx)
processed = self._process_stage_inputs(config, args, current_output)
stage_output = self._execute_stage(engine, processed, stage_config=config)
current_output = stage_output
config.stage_output = stage_output
return self.output_processor.process_output(current_output)
```
### 4. Stage config for setting of different model stages
### 5. Stage Manager
```python
# vllm_omni/core/stage_manager.py
class StageManager:
def initialize_engines(self):
if self._initialized:
return
for config in self.stage_configs:
if config.engine_type == "AR":
engine = self._create_ar_engine(config)
elif config.engine_type == "DiT":
engine = self._create_dit_engine(config)
self.engine_list.append(engine)
self._initialized = True
```
### 6. Diffusion Execution (DiT Stage)
```python
# vllm_omni/worker/diffusion_model_runner.py
class DiffusionModelRunner:
def __init__(self, model_path, pipeline_name=None, device=None, dtype=None):
self.device, self.torch_dtype = self._resolve_device_and_dtype(device, dtype)
self._pipeline = self._load_pipeline(model_path, pipeline_name)
def generate(...):
output = self._pipeline(...)
return DiffusionRunnerOutput(prompt=prompt, images=getattr(output, "images", []) or [])
```
```python
# vllm_omni/worker/gpu_diffusion_worker.py
class DiffusionGPUWorker:
def __init__(self, model_path, ...):
self.model_runner = DiffusionModelRunner(model_path, ...)
def generate(...):
return self.model_runner.generate(...)
```
```python
# vllm_omni/executor/diffusers_executor.py
class DiffusersPipelineExecutor:
def __init__(self, model_path, ...):
self.worker = DiffusionGPUWorker(model_path, ...)
def generate(...):
return self.worker.generate(...)
```
This mirrors the standard executor→worker→runner layering in vLLM.
### 7. Stage Configuration
```python
# vllm_omni/config/stage_config.py
@dataclass
class OmniStageConfig:
"""Configuration for a processing stage"""
stage_id: int
engine_type: str # "AR" or "DiT"
engine_type: Literal["AR", "DiT"]
model_path: str
input_modalities: List[str]
output_modalities: List[str]
vllm_config: Optional[VllmConfig] = None # For engine config of corresponding stage
executor_class: type[Executor] # For execute class config of corresponding stage
dit_config: Optional[DiTConfig] = None # For diffusion stages
cache_config: Optional[DiTCacheConfig] = None
vllm_config: Optional[VllmConfig] = None
executor_class: Optional[type[Executor]] = None
dit_config: Optional[DiTConfig] = None
```
For AR stage, the setting is:
```python
from vllm.v1.core.sched.scheduler import Scheduler
from vllm.v1.executor.multiproc_executor import MultiprocExecutor
ar_stage_config = OmniStageConfig()
ar_stage_config.vllm_config.scheduler_config.scheduler_cls = Scheduler # original vllm scheduler for AR
ar_stage_config.executor_class = MultiprocExecutor # original vllm executor for AR
```
For DiT stage, the setting is:
```python
from vllm.v1.executor.multiproc_executor import MultiprocExecutor
dit_stage_config = OmniStageConfig()
dit_stage_config.vllm_config.scheduler_config.scheduler_cls = DiffusionScheduler # New scheduler for DiT
# For diffusion models without using diffusers
dit_stage_config.executor_class = MultiprocExecutor
- AR stages default to `UniProcExecutor`.
- DiT stages set `executor_class` to `DiffusersPipelineExecutor` when `use_diffusers=True`.
# For diffusion models using diffusers
dit_stage_config.executor_class = DiffusersPipelineExecutor
```
### 5. Online inference main class
Similar to OmniLLM in offline inference, add some asynchronous processing, referring to AsyncLLM
```python
from vllm.v1.engine.async_llm import AsyncLLM
class AsyncOmniLLM(AsyncLLM):
"""Extended AsyncLLM supporting multiple engines and stage-based processing"""
def __init__(self, stage_configs: List[OmniStageConfig]):
super().__init__()
```
### 6. Engine Core
No need to change. The specific executor, scheduler and output type can be transferred into it with new configs.
```python
# class: EngineCore
# EngineCore.step (simplified)
class EngineCore:
def step(self):
scheduler_output = scheduler.schedule()
model_output = executor.execute_model(scheduler_output)
engine_outputs = scheduler.update_from_output(
scheduler_output, model_output
)
return engine_outputs
```
### 7. Scheduler
Create a new Diffusion Scheduler for DiT, which is inherited from original vllm AR scheduler. At first, no complicated strategy.
Just process the request first in first out. Then create a class of DiT Cache Manager for optimization in the future.
```python
from vllm.v1.core.sched.scheduler import SchedulerInterface
class OmniDiffusionScheduler(SchedulerInterface):
def __init__(
self,
vllm_config: VllmConfig,
kv_cache_config: KVCacheConfig,
dit_cache_config: DiTCacheConfig,
structured_output_manager: StructuredOutputManager,
mm_registry: MultiModalRegistry = MULTIMODAL_REGISTRY,
include_finished_set: bool = False,
log_stats: bool = False,
) -> None:
self.dit_cache_manager = DiTCacheManager(dit_cache_config)
```
For DiT Cache Manager (Can refer to xDiT):
```python
class DiTCacheManager:
"""Manages DiT-specific caching"""
def __init__(self, config: DiTCacheConfig):
self.cache_tensors: Dict[str, torch.Tensor] = {}
self.cache_groups: List[DiTCacheTensor] = config.dit_cache_tensors
def allocate_cache(self, request_id: str, size: int) -> torch.Tensor
def get_cache(self, request_id: str) -> Optional[torch.Tensor]
def release_cache(self, request_id: str) -> None
def clear_expired_cache(self) -> None
```
### 8. Executor for DiT without diffusers
No need to change. Just import original AR executor.
### 9. Worker
#### Inheritance strategy
Prefer reusing the mature GPU Worker end-to-end. Worker class is selected by configuration (vllm_config). Do not add a new executor-specific worker binding. If customization is needed, override only `init_device` to construct the `OmniDiffusionModelRunner`; all other behaviors (device init details, profiling, sleep/wake, PP/TP comms, execute path) remain from `vllm/v1/worker/gpu_worker.py::Worker`.
Inherited and overridden
```python
# Optional: only if you need to plug a custom OmniDiffusionModelRunner.
from vllm.v1.worker.gpu_worker import Worker as GPUWorker
from vllm.v1.worker.diffusion_model_runner import OmniDiffusionModelRunner
class Worker(GPUWorker):
def init_device(self) -> None:
#those related to device check and init
...
self.model_runner = OmniDiffusionModelRunner(self.vllm_config, ...)
```
### 10. Model Runner
### Function map (Model Runner)
#### 1) Inherited and overridden
Those parts relied to the KV Cache will be omitted if we do not register the model to the vllm config. The engine core will view it as do not require KV Cache, and handle it properly
Reuse `vllm/v1/outputs.py::ModelRunnerOutput`:
- OmniDiffusionModelRunner: Use the `pooler_output=[Tensor,...]` to return multi modal tensors
- OmniARModelRunner: Use the `pooler_output=[Tensor,...]` to return hidden states.
```python
from typing import Optional, Union
import torch
from vllm.v1.worker.gpu_model_runner import GPUModelRunner
from vllm.v1.outputs import ModelRunnerOutput
class OmniDiffusionModelRunner(GPUModelRunner):
@torch.inference_mode()
def execute_model(
self,
scheduler_output: "SchedulerOutput",
intermediate_tensors: Optional[IntermediateTensors] = None,
) -> Union[ModelRunnerOutput, IntermediateTensors]:
...
return ModelRunnerOutput(
req_ids=[...],
req_id_to_index={...},
sampled_token_ids=[],
spec_token_ids=None,
logprobs=None,
prompt_logprobs_dict={},
pooler_output=[Tensor,...], # Return Hidden states
kv_connector_output=None,
num_nans_in_logits=None,
)# return multi modal tensors via pooler_output=[Tensor,...]
class OmniARModelRunner(GPUModelRunner):
@torch.inference_mode()
def execute_model(
self,
scheduler_output: "SchedulerOutput",
intermediate_tensors: Optional[IntermediateTensors] = None,
) -> Union[ModelRunnerOutput, IntermediateTensors]:
...
return ModelRunnerOutput(
req_ids=[...],
req_id_to_index={...},
sampled_token_ids=[],
spec_token_ids=None,
logprobs=None,
prompt_logprobs_dict={},
pooler_output=[Tensor,...], # Return Hidden states
kv_connector_output=None,
num_nans_in_logits=None,
)# return hidden states via pooler_output=[Tensor,...]
```
### 11. RequestState Role and Usage
```bash
# RequestState serves as the per-request state tracker in OutputProcessor:
# - Maintains request-specific state (tokens, logprobs, detokenizer, etc.)
# - Converts EngineCoreOutput → RequestOutput/PoolingRequestOutput
# - Manages request lifecycle from registration to completion
AsyncLLM.add_request()
↓
OutputProcessor.add_request()
↓
RequestState.from_new_request() → 创建请求状态
AsyncLLM.__init__() / AsyncLLM.generate() / AsyncLLM.encode() → 创建一个Background loop 持续从EngineCore获取输出
↓
OutputProcessor.process_outputs() → 更新状态并处理输出
↓
RequestState.make_request_output() → 转换为最终输出,格式为RequestOutput或者PoolingRequestOutput
↓
RequestOutputCollector.put() → 推送到队列(AsyncLLM)
```
Need to add implementation for an existing method
```python
class RequestState:
def __init__(self, request_id: str, parent_req: Optional[ParentRequest],
request_index: int, lora_name: Optional[str],
output_kind: RequestOutputKind, prompt: Optional[str],
prompt_token_ids: list[int], logprobs_processor: Optional[LogprobsProcessor],
detokenizer: Optional[IncrementalDetokenizer],
max_tokens_param: Optional[int], arrival_time: float,
queue: Optional[RequestOutputCollector], log_stats: bool):
def _new_pooling_output(self, pooling_output: torch.Tensor) -> PoolingOutput:
"""Create PoolingOutput for multimodal/pooling requests"""
return PoolingOutput(data=pooling_output)
```
### 12. OutputProcessor
For hidden state output, original OutputProcessor already support.
We only need to add new one for final multimodal output.
### 8. Output Processing
```python
# vllm_omni/engine/output_processor.py
class MultimodalOutputProcessor(OutputProcessor):
"""Handles multimodal output processing"""
def __init__(self):
self.output_handlers: Dict[str, OutputProcessor] = {}
def process_outputs(self, engine_core_outputs: list[EngineCoreOutput], ...):
for engine_core_output in engine_core_outputs:
# Option 1: Use output_type field (if EngineCoreOutput is extended)
if engine_core_output.output_type == "image":
self._process_image_output(engine_core_output)
elif engine_core_output.output_type == "text+image":
self._process_text_image_output(engine_core_output)
elif engine_core_output.output_type == "latents":
self._process_latents_output(engine_core_output)
elif engine_core_output.output_type == "text":
self._process_text_output(engine_core_output)
else:
# Fallback: use existing pooling_output logic
if engine_core_output.pooling_output is not None:
self._process_pooling_output(engine_core_output)
else:
self._process_text_output(engine_core_output)
def process_output(self, engine_core_output):
output_type = self._detect_output_type(engine_core_output)
handler = self.output_handlers.get(output_type, self._process_pooling_output)
return handler(engine_core_output)
```
### 9. CLI Package Configuration
```toml
# pyproject.toml
[project.scripts]
vllm = "vllm_omni.entrypoints.cli.main:main"
vllm-omni = "vllm_omni.entrypoints.cli.main:main"
```
## Future Work
- Scheduler adaptations for diffusion batching.
- Advanced output processors for audio/video.
- Multi-worker execution (ray / multiprocess) for heavy DiT stages.
+1 -1
View File
@@ -136,4 +136,4 @@ Hongsheng Liu: [hsliuustc@gmail.com](mailto:hsliuustc@gmail.com)
[image10]: <data:image/png;base64,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 truncated
[image11]: <data:image/png;base64,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 truncated
[image11]: <data:image/png;base64,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 truncated
@@ -12,13 +12,12 @@ vllm_omni/
│ └── cache_config.py # Caching configurations
├── core/ # Core processing components
│ ├── __init__.py
│ ├── omni_llm.py # OmniLLM and AsyncOmniLLM
│ ├── stage_manager.py # Multi-stage orchestration
│ ├── dit_cache_manager.py # DiT caching system
│ └── sched/ # Schedulers
│ ├── __init__.py
│ ├── scheduler.py # Base scheduler interface
│ └── diffusion_scheduler.py # DiT scheduler
├── engine/ # Engine components
│ ├── __init__.py
│ ├── processor.py # Output processing
@@ -29,14 +28,14 @@ vllm_omni/
│ └── diffusers_executor.py # Diffusers pipeline executor
├── model_executor/ # Model execution components
│ ├── __init__.py
│ ├── ar_model_runner.py # OmniARModelRunner
│ └── dit_model_runner.py # OmniDiffusionModelRunner
├── worker/ # Worker implementations
│ ├── __init__.py
│ └── omni_worker.py # Extended worker for DiT
│ ├── gpu_diffusion_model_runner.py # Loads and runs diffusers pipelines
│ └── gpu_diffusion_worker.py # Worker facade around the diffusion runner
├── entrypoints/ # Entry points and CLI
│ ├── __init__.py
│ ├── omni.py # OmniServeCommand
│ ├── omni_llm.py # OmniLLM and AsyncOmniLLM
│ └── cli/ # CLI integration
│ ├── __init__.py
│ └── main.py # CLI main entry point
@@ -87,24 +86,29 @@ class OmniStageConfig:
input_modalities: List[str]
output_modalities: List[str]
vllm_config: Optional[VllmConfig] = None
executor_class: type[Executor] = MultiprocExecutor
dit_config: Optional[DiTConfig] = None
cache_config: Optional[DiTCacheConfig] = None
executor_class: Optional[type[Executor]] = None
stage_output: Optional[Any] = None
```
### 3.2 DiT Configuration
```python
# vllm_omni/config/dit_config.py
# vllm_omni/config/stage_config.py
@dataclass
class DiTConfig:
model_type: str
scheduler_type: str
model_config: Optional[ModelConfig] = None
scheduler_config: Optional[Any] = None
device_config: Optional[DeviceConfig] = None
load_config: Optional[LoadConfig] = None
compilation_config: Optional[CompilationConfig] = None
dit_cache_config: Optional[DiTCacheConfig] = None
num_inference_steps: int
guidance_scale: float
guidance_scale: float = 7.5
use_diffusers: bool = False
diffusers_pipeline: Optional[str] = None
height: int = 512
width: int = 512
```
### 3.3 Cache Configuration
@@ -179,34 +183,9 @@ class AsyncOmniLLM(AsyncLLM):
"""Async generation interface"""
# Similar to OmniLLM but with async/await patterns
pass
```
``
### 4.3 DiT Scheduler Implementation
```python
# vllm_omni/core/sched/diffusion_scheduler.py
class OmniDiffusionScheduler(SchedulerInterface):
def __init__(
self,
vllm_config: VllmConfig,
kv_cache_config: KVCacheConfig,
dit_cache_config: DiTCacheConfig,
structured_output_manager: StructuredOutputManager,
mm_registry: MultiModalRegistry = MULTIMODAL_REGISTRY,
include_finished_set: bool = False,
log_stats: bool = False,
) -> None:
super().__init__(vllm_config, kv_cache_config, structured_output_manager,
mm_registry, include_finished_set, log_stats)
self.dit_cache_manager = DiTCacheManager(dit_cache_config)
def schedule(self) -> SchedulerOutput:
"""Schedule DiT requests with caching optimization"""
# Implement DiT-specific scheduling logic
pass
```
### 4.4 Model Runners
### 4.3 Model Runners
```python
# vllm_omni/model_executor/dit_model_runner.py
-293
View File
@@ -1,293 +0,0 @@
# vLLM-omni Implementation Summary
## Overview
This document provides a comprehensive summary of the vLLM-omni implementation plan and current progress. The implementation follows a structured approach with PRD, architecture design, test design, and code implementation phases.
## Implementation Status
### ✅ Completed Phases
#### 1. Product Requirements Document (PRD)
- **File**: `docs/PRD.md`
- **Status**: Complete
- **Contents**:
- Product vision and target users
- Functional and non-functional requirements
- Technical architecture overview
- Implementation phases and success criteria
- Risk assessment and mitigation strategies
#### 2. Architecture Design
- **File**: `docs/architecture/implementation_architecture.md`
- **Status**: Complete
- **Contents**:
- Detailed package structure
- Core module dependencies
- Configuration system design
- Implementation details for all components
- Memory management and error handling strategies
#### 3. Test Design
- **File**: `docs/testing/test_design.md`
- **Status**: Complete
- **Contents**:
- Comprehensive testing strategy
- Unit, integration, and E2E test specifications
- Performance and compatibility testing
- Test configuration and execution guidelines
#### 4. Package Setup
- **Status**: Complete
- **Components**:
- Updated `pyproject.toml` with vLLM integration
- Package structure and dependencies
- CLI entry point configuration
- vLLM plugin system setup
#### 5. Core Modules
- **Status**: Complete
- **Components**:
- `vllm_omni/config/`: Configuration management
- `vllm_omni/core/omni_llm.py`: OmniLLM and AsyncOmniLLM classes
- `vllm_omni/core/stage_manager.py`: Multi-stage orchestration
- `vllm_omni/core/dit_cache_manager.py`: DiT caching system
- `vllm_omni/core/sched/diffusion_scheduler.py`: DiT scheduler
- `vllm_omni/engine/output_processor.py`: Multimodal output processing
### 🚧 In Progress / Pending
#### 6. Scheduler and Executor Components
- **Status**: Partially Complete
- **Completed**:
- DiT scheduler implementation
- DiT cache manager
- **Pending**:
- Executor implementations
- Integration with vLLM's executor system
#### 7. Model Runners
- **Status**: Pending
- **Components**:
- `OmniDiffusionModelRunner`: DiT model execution
- `OmniARModelRunner`: AR model execution with hidden states
- Integration with vLLM's model runner system
#### 8. Output Processing
- **Status**: Partially Complete
- **Completed**:
- Basic multimodal output processor
- **Pending**:
- RequestState extensions
- Advanced output handling
#### 9. CLI Integration
- **Status**: Complete
- **Components**:
- CLI entry point with `--omni` flag support
- OmniServeCommand implementation
- API server for serving
- vLLM plugin system
#### 10. Testing and Validation
- **Status**: Partially Complete
- **Completed**:
- Basic test structure
- Configuration tests
- **Pending**:
- Integration tests
- E2E tests
- Performance benchmarks
## Key Implementation Details
### Package Structure
```
vllm_omni/
├── config/ # Configuration management
│ ├── stage_config.py # OmniStageConfig, DiTConfig, etc.
│ └── __init__.py
├── core/ # Core processing components
│ ├── omni_llm.py # OmniLLM and AsyncOmniLLM
│ ├── stage_manager.py # Multi-stage orchestration
│ ├── dit_cache_manager.py # DiT caching system
│ └── sched/ # Schedulers
│ └── diffusion_scheduler.py
├── engine/ # Engine components
│ └── output_processor.py # Multimodal output handling
├── entrypoints/ # Entry points and CLI
│ ├── cli/main.py # CLI main entry point
│ ├── omni.py # OmniServeCommand
│ └── api_server.py # API server
├── plugin.py # vLLM plugin system
└── __init__.py # Main package exports
```
### Core Classes
#### OmniLLM
- **Purpose**: Offline multi-stage processing
- **Features**:
- Sequential stage processing
- AR and DiT stage support
- Input/output processing between stages
- Integration with vLLM's LLMEngine
#### AsyncOmniLLM
- **Purpose**: Online multi-stage processing
- **Features**:
- Asynchronous stage processing
- Real-time request handling
- Integration with vLLM's AsyncLLM
#### StageManager
- **Purpose**: Orchestrate multiple stage engines
- **Features**:
- Engine creation and management
- Stage configuration handling
- Resource cleanup
#### DiTCacheManager
- **Purpose**: Optimize DiT inference with caching
- **Features**:
- Tensor caching with multiple strategies (FIFO, LRU, LFU)
- Memory management
- Cache statistics and monitoring
### Configuration System
#### OmniStageConfig
- **Purpose**: Configure individual processing stages
- **Features**:
- Support for AR and DiT engine types
- Modality specification
- Executor class selection
- DiT-specific configuration
#### DiTConfig
- **Purpose**: Configure DiT-specific parameters
- **Features**:
- Model type and scheduler configuration
- Inference parameters (steps, guidance scale)
- Diffusers integration support
### CLI Integration
#### Command Line Interface
```bash
# Basic usage
vllm serve Qwen/Qwen2.5-Omni-7B --omni --port 8000
# Multi-stage configuration
vllm serve model --omni --ar-stage text-model --dit-stage image-model
# DiT-specific options
vllm serve model --omni --dit-steps 50 --dit-guidance-scale 7.5
```
#### API Server
- **Endpoints**:
- `POST /generate`: Generate text or multimodal content
- `GET /health`: Health check
- `GET /info`: Service information
- **Features**:
- FastAPI-based REST API
- Async request handling
- Multi-stage processing support
## Installation and Usage
### Installation
```bash
# Install vLLM first
pip install vllm>=0.2.0
# Install vLLM-omni
pip install vllm-omni
# Or install from source
git clone https://github.com/hsliuustc0106/vllm-omni
cd vllm-omni
pip install -e ".[dev]"
```
### Basic Usage
```python
from vllm_omni import OmniLLM, create_ar_stage_config, create_dit_stage_config
# Create stage configurations
ar_config = create_ar_stage_config(
stage_id=0,
model_path="Qwen/Qwen3-0.6B",
input_modalities=["text"],
output_modalities=["text"]
)
dit_config = create_dit_stage_config(
stage_id=1,
model_path="stabilityai/stable-diffusion-2-1",
input_modalities=["text"],
output_modalities=["image"]
)
# Create OmniLLM instance
omni_llm = OmniLLM([ar_config, dit_config])
# Generate with multi-stage processing
stage_args = [
{"prompt": "A beautiful landscape"},
{"prompt": "A beautiful landscape"}
]
outputs = omni_llm.generate(stage_args)
```
## Next Steps
### Immediate Priorities
1. **Complete Model Runners**: Implement OmniDiffusionModelRunner and OmniARModelRunner
2. **Executor Integration**: Complete executor implementations and vLLM integration
3. **Testing**: Implement comprehensive test suite
4. **Documentation**: Create user guides and API documentation
### Medium-term Goals
1. **Performance Optimization**: Optimize memory usage and inference speed
2. **Advanced Features**: Implement advanced scheduling and caching strategies
3. **Model Support**: Add support for more model architectures
4. **Production Readiness**: Add monitoring, logging, and deployment tools
### Long-term Vision
1. **Multi-GPU Support**: Optimize for distributed inference
2. **Custom Architectures**: Support for custom model architectures
3. **Advanced Multimodal**: Enhanced multimodal fusion capabilities
4. **Ecosystem Integration**: Integration with popular ML frameworks
## Technical Considerations
### vLLM Compatibility
- Maintains compatibility with vLLM V1 architecture
- Reuses proven components (scheduler, executor, worker patterns)
- Minimal modifications to existing vLLM codebase
### Memory Management
- Efficient caching system for DiT models
- Memory sharing between stages
- Garbage collection optimization
### Extensibility
- Plugin-based architecture
- Easy integration of new modalities
- Configurable stage processing
### Performance
- Optimized for both AR and DiT models
- Efficient multi-stage processing
- Scalable to multiple GPUs
## Conclusion
The vLLM-omni implementation provides a solid foundation for multi-modality model inference with non-autoregressive structures. The current implementation covers the core functionality needed for basic multi-stage processing, with a clear path forward for advanced features and optimizations.
The modular architecture ensures maintainability and extensibility, while the vLLM integration provides a proven foundation for production deployment. The comprehensive testing strategy and documentation ensure reliability and ease of use.
This implementation represents a significant step forward in making advanced multimodal AI models accessible and efficient for production use cases.
+17 -18
View File
@@ -100,9 +100,9 @@ class TestOmniStageConfig:
def test_dit_stage_config_creation(self):
"""Test DiT stage configuration creation"""
dit_config = DiTConfig(
model_type="dit",
scheduler_type="ddpm",
num_inference_steps=50
num_inference_steps=50,
guidance_scale=7.5,
use_diffusers=False,
)
config = OmniStageConfig(
stage_id=1,
@@ -130,27 +130,24 @@ class TestOmniStageConfig:
#### test_dit_config.py
```python
import pytest
from vllm_omni.config.dit_config import DiTConfig
from vllm.config import ModelConfig
from vllm_omni.config import DiTConfig
class TestDiTConfig:
def test_dit_config_defaults(self):
"""Test DiT configuration with defaults"""
config = DiTConfig(
model_type="dit",
scheduler_type="ddpm",
num_inference_steps=50
)
config = DiTConfig(num_inference_steps=50)
assert config.model_config is None
assert config.use_diffusers is False
assert config.diffusers_pipeline is None
def test_diffusers_config(self):
"""Test DiT configuration with diffusers"""
config = DiTConfig(
model_type="dit",
scheduler_type="ddpm",
model_config=ModelConfig(model="stabilityai/stable-diffusion-2-1"),
num_inference_steps=50,
use_diffusers=True,
diffusers_pipeline="stable-diffusion"
diffusers_pipeline="stable-diffusion",
)
assert config.use_diffusers is True
assert config.diffusers_pipeline == "stable-diffusion"
@@ -431,6 +428,7 @@ class TestCLIIntegration:
#### test_full_pipeline.py
```python
import pytest
from vllm.config import ModelConfig
from vllm_omni.core.omni_llm import OmniLLM
from vllm_omni.config.stage_config import OmniStageConfig, DiTConfig
@@ -454,9 +452,10 @@ class TestFullPipeline:
input_modalities=["text"],
output_modalities=["image"],
dit_config=DiTConfig(
model_type="dit",
scheduler_type="ddpm",
num_inference_steps=10 # Reduced for testing
model_config=ModelConfig(model="stabilityai/stable-diffusion-2-1"),
num_inference_steps=10,
guidance_scale=7.5,
use_diffusers=False,
)
)
]
@@ -631,9 +630,9 @@ def sample_stage_configs():
input_modalities=["text"],
output_modalities=["image"],
dit_config=DiTConfig(
model_type="dit",
scheduler_type="ddpm",
num_inference_steps=10
model_config=ModelConfig(model="test-dit-model"),
num_inference_steps=10,
guidance_scale=7.5,
)
)
]
+11 -44
View File
@@ -1,49 +1,16 @@
# vLLM-omni Examples
This directory contains examples demonstrating how to use vLLM-omni for multi-modality model inference and serving.
A curated set of runnable samples demonstrating vLLM-omni usage.
## Directory Structure
## Directory Overview
- `basic/` - Simple usage examples and API client
- `advanced/` - Advanced features and configurations
- `multimodal/` - Multi-modality specific examples
- **basic/** – Quick-start scripts for text-only flows.
- `text_generation.py` – Minimal CPU-friendly text generation with vLLM.
- `api_client.py` – Calls the FastAPI server using the JSON API.
- `docker_run.py` – Template for launching the server in Docker.
- **omni/** – Multi-stage AR → DiT pipelines powered by diffusers.
- `ar_dit_diffusers.py` – Runs Qwen3 (AR) feeding Stable Diffusion 2.1 (DiT).
- `configs/ar_dit_local.yaml` – Sample plugin config referencing local weights.
- **advanced/** – Placeholder for upcoming complex flows (Ray, benchmarking).
## Quick Start
1. **Basic Usage:**
```bash
cd examples/basic
python simple_usage.py
```
2. **API Server:**
```bash
# Start server
vllm serve Qwen/Qwen3-0.6B --omni --port 8000
# Test with client
python api_client.py
```
## Examples Overview
### Basic Examples (`basic/`)
- `simple_usage.py` - Direct library usage (sync/async)
- `api_client.py` - HTTP API client examples
- `README.md` - Basic examples documentation
### Advanced Examples (`advanced/`)
- Multi-stage processing
- Custom configurations
- Performance optimization
### Multi-modality Examples (`multimodal/`)
- Text-to-image generation
- Image-to-text processing
- Multi-modal pipelines
## Requirements
- Python 3.8+
- vLLM-omni installed
- For API examples: HTTP server running
See the subdirectory READMEs for usage instructions.
+6 -72
View File
@@ -1,76 +1,10 @@
# Basic vLLM-omni Examples
# Basic Examples
This directory contains simple examples demonstrating how to use vLLM-omni for text generation.
Quick-start scripts for experimenting with vLLM-omni.
## Examples
## Scripts
### 1. `simple_usage.py` - Direct Library Usage
Demonstrates how to use vLLM-omni directly in your Python code:
```bash
# Run the example
python simple_usage.py
```
**Features:**
- Synchronous text generation
- Asynchronous text generation
- Multi-prompt processing
- Shows both text output and token information
### 2. `api_client.py` - API Server Usage
Demonstrates how to interact with the vLLM-omni API server:
```bash
# First, start the server
vllm serve Qwen/Qwen3-0.6B --omni --port 8000
# Then run the client (in another terminal)
python api_client.py
```
**Features:**
- Health check
- Text generation via HTTP API
- Multiple prompt types
- Full response structure inspection
## Quick Start
1. **Install vLLM-omni:**
```bash
pip install -e .
```
2. **Run direct usage example:**
```bash
cd examples/basic
python simple_usage.py
```
3. **Run API server example:**
```bash
# Terminal 1: Start server
vllm serve Qwen/Qwen3-0.6B --omni --port 8000
# Terminal 2: Run client
cd examples/basic
python api_client.py
```
## Expected Output
The examples will show:
- ✅ Generated text responses
- ✅ Token counts and completion status
- ✅ Stage processing information
- ✅ Error handling
## Requirements
- Python 3.8+
- vLLM-omni installed
- For API examples: HTTP server running
- `text_generation.py` – Minimal standalone usage showing how to create an LLM and generate text locally.
- `api_client.py` – Demonstrates calling the FastAPI `/generate` endpoint from Python.
Run any script with `python examples/basic/<script>.py`. Ensure the `vllm_omni` conda environment is active and dependencies are installed.
-178
View File
@@ -1,178 +0,0 @@
#!/usr/bin/env python3
"""
Docker example for vLLM-omni.
This example shows how to test the vLLM-omni API server running in Docker.
"""
import requests
import json
import time
import subprocess
import sys
def check_docker_running():
"""Check if Docker is running."""
try:
result = subprocess.run(['docker', 'info'],
capture_output=True, text=True, timeout=5)
return result.returncode == 0
except (subprocess.TimeoutExpired, FileNotFoundError):
return False
def start_docker_server():
"""Start the vLLM-omni server in Docker."""
print("🐳 Starting vLLM-omni server in Docker...")
# Check if container is already running
result = subprocess.run(['docker', 'ps', '--filter', 'name=vllm-omni-cpu', '--format', '{{.Names}}'],
capture_output=True, text=True)
if 'vllm-omni-cpu' in result.stdout:
print("✅ Server already running")
return True
# Start the server
cmd = [
'docker', 'run', '-d', '--name', 'vllm-omni-cpu',
'-p', '8000:8000',
'vllm-omni-cpu:latest',
'vllm', 'serve', 'Qwen/Qwen3-0.6B', '--omni', '--port', '8000'
]
try:
subprocess.run(cmd, check=True)
print("✅ Server started successfully")
# Wait for server to be ready
print("⏳ Waiting for server to be ready...")
for i in range(30): # Wait up to 30 seconds
if test_server_health():
print("✅ Server is ready!")
return True
time.sleep(1)
print("❌ Server failed to start within 30 seconds")
return False
except subprocess.CalledProcessError as e:
print(f"❌ Failed to start server: {e}")
return False
def test_server_health():
"""Test if the server is healthy."""
try:
response = requests.get("http://localhost:8000/health", timeout=2)
return response.status_code == 200
except requests.exceptions.RequestException:
return False
def test_generation():
"""Test text generation via API."""
print("\n🧪 Testing text generation...")
test_cases = [
{
"prompt": "Hello, how are you?",
"max_tokens": 50,
"temperature": 0.7
},
{
"prompt": "Tell me a joke",
"max_tokens": 100,
"temperature": 0.9
},
{
"prompt": "What is artificial intelligence?",
"max_tokens": 150,
"temperature": 0.5
}
]
for i, test_case in enumerate(test_cases, 1):
print(f"\nTest {i}: {test_case['prompt']}")
try:
response = requests.post(
"http://localhost:8000/generate",
json={
"prompts": [test_case["prompt"]],
"max_tokens": test_case["max_tokens"],
"temperature": test_case["temperature"]
},
timeout=30
)
if response.status_code == 200:
result = response.json()
text = result["outputs"][0]["text"]
print(f"✅ Response: {text}")
else:
print(f"❌ Error: {response.status_code} - {response.text}")
except requests.exceptions.RequestException as e:
print(f"❌ Request failed: {e}")
def cleanup():
"""Clean up Docker container."""
print("\n🧹 Cleaning up...")
try:
subprocess.run(['docker', 'stop', 'vllm-omni-cpu'],
capture_output=True, text=True)
subprocess.run(['docker', 'rm', 'vllm-omni-cpu'],
capture_output=True, text=True)
print("✅ Cleanup completed")
except Exception as e:
print(f"⚠️ Cleanup warning: {e}")
def main():
"""Main example function."""
print("🐳 vLLM-omni Docker Example")
print("=" * 40)
# Check Docker
if not check_docker_running():
print("❌ Docker is not running. Please start Docker Desktop.")
sys.exit(1)
print("✅ Docker is running")
# Check if image exists
result = subprocess.run(['docker', 'images', 'vllm-omni-cpu:latest'],
capture_output=True, text=True)
if 'vllm-omni-cpu' not in result.stdout:
print("❌ Docker image not found. Please build it first:")
print(" docker build -f docker/Dockerfile.cpu -t vllm-omni-cpu .")
sys.exit(1)
print("✅ Docker image found")
try:
# Start server
if not start_docker_server():
sys.exit(1)
# Test generation
test_generation()
print("\n🎉 Docker example completed successfully!")
except KeyboardInterrupt:
print("\n⏹️ Example interrupted by user")
finally:
# Ask if user wants to keep server running
keep_running = input("\n🤔 Keep the server running? (y/N): ").lower().strip()
if keep_running != 'y':
cleanup()
else:
print("✅ Server kept running. Access it at http://localhost:8000")
if __name__ == "__main__":
main()
File renamed without changes.
+31
View File
@@ -0,0 +1,31 @@
# Omni Examples
Examples showcasing multi-stage AR + DiT pipelines powered by vLLM-omni.
## AR ➜ DiT with Diffusers Backend
```bash
# Optional: customise models
export AR_MODEL=Qwen/Qwen3-0.6B
export DIT_MODEL=./models/stable-diffusion-2-1/
# Run with defaults (20 steps @ 512x512)
python examples/omni/ar_dit_diffusers.py
# Faster run
python examples/omni/ar_dit_diffusers.py --steps 14 --height 384 --width 384 --guidance 4.5
```
Arguments:
- `--ar-model`: Override AR stage model (default `Qwen/Qwen3-0.6B`).
- `--dit-model`: Override DiT stage model (default env `DIT_MODEL`, else diffusers repo).
- `--steps`: Number of diffusion steps (default 20).
- `--guidance`: Guidance scale (default 5.0).
- `--height` / `--width`: Output resolution (default 512).
- `--seed`: Optional RNG seed.
- `--prompt`, `--temperature`, `--max-tokens`: Control AR stage generation.
- `--output`: Destination path for the generated image file.
Generated images are saved next to the script as `omni_dit_output.png` by default.
See the script for additional tweaks (scheduler, prompt chaining, etc.).
+175
View File
@@ -0,0 +1,175 @@
"""Run the AR → DiT (diffusers) pipeline using YAML configuration defaults."""
from __future__ import annotations
import argparse
import os
from pathlib import Path
from typing import Dict, List
import yaml
from vllm_omni import (
OmniLLM,
DiTConfig,
create_ar_stage_config,
create_dit_stage_config,
)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Run a two-stage AR → DiT pipeline with config-derived defaults."
)
parser.add_argument(
"--config",
type=Path,
default=Path(__file__).resolve().parent / "configs" / "ar_dit_local.yaml",
help="Path to a YAML configuration describing the pipeline stages.",
)
parser.add_argument(
"--prompt",
default="A scenic watercolor painting of a lighthouse at sunset",
help="Prompt passed to the AR stage.",
)
parser.add_argument(
"--negative-prompt",
default=None,
help="Optional negative prompt forwarded to the diffusion stage.",
)
parser.add_argument(
"--seed",
type=int,
default=None,
help="Optional seed for deterministic diffusion sampling.",
)
parser.add_argument(
"--output",
type=Path,
default=Path("./omni_dit_output.png"),
help="Destination path for the generated image.",
)
return parser.parse_args()
def _normalize_modalities(values: List[str] | None, default: List[str]) -> List[str]:
if not values:
return default
return list(values)
def _load_stage_configs_from_yaml(config_path: Path):
with config_path.open("r", encoding="utf-8") as f:
config = yaml.safe_load(f) or {}
stages = config.get("stages", [])
if not stages:
raise ValueError(f"No stages defined in config: {config_path}")
stage_configs = []
for stage in stages:
stage_id = stage["stage_id"]
engine_type = stage["engine_type"]
default_stage_args = stage.get("default_stage_args")
if engine_type == "AR":
stage_configs.append(
create_ar_stage_config(
stage_id=stage_id,
model_path=stage["model_path"],
input_modalities=_normalize_modalities(
stage.get("input_modalities"), ["text"]
),
output_modalities=_normalize_modalities(
stage.get("output_modalities"), ["text"]
),
default_stage_args=default_stage_args,
)
)
elif engine_type == "DiT":
dit_cfg_dict: Dict = dict(stage.get("dit_config", {}))
if "num_inference_steps" not in dit_cfg_dict:
raise ValueError(
"DiT stage requires 'num_inference_steps' in dit_config"
)
dit_cfg = DiTConfig(**dit_cfg_dict)
stage_configs.append(
create_dit_stage_config(
stage_id=stage_id,
model_path=stage["model_path"],
input_modalities=_normalize_modalities(
stage.get("input_modalities"), ["text"]
),
output_modalities=_normalize_modalities(
stage.get("output_modalities"), ["image"]
),
dit_config=dit_cfg,
default_stage_args=default_stage_args,
)
)
else:
raise ValueError(f"Unsupported engine_type '{engine_type}' in config")
return stage_configs
def _apply_env_overrides(stage_configs):
ar_override = os.environ.get("AR_MODEL")
dit_override = os.environ.get("DIT_MODEL")
for stage_config in stage_configs:
if ar_override and stage_config.engine_type == "AR":
stage_config.model_path = ar_override
if dit_override and stage_config.engine_type == "DiT":
stage_config.model_path = dit_override
def main():
args = parse_args()
stage_configs = _load_stage_configs_from_yaml(args.config)
_apply_env_overrides(stage_configs)
omni = OmniLLM(stage_configs)
stage_overrides: Dict[int, Dict[str, object]] = {}
if args.seed is not None or args.negative_prompt:
for stage_config in stage_configs:
if stage_config.engine_type != "DiT":
continue
override = stage_overrides.setdefault(stage_config.stage_id, {})
if args.seed is not None:
override["seed"] = args.seed
if args.negative_prompt:
override["negative_prompt"] = args.negative_prompt
outputs = omni.generate(
prompt=args.prompt,
stage_overrides=stage_overrides if stage_overrides else None,
)
image = None
for request_output in outputs:
for completion in getattr(request_output, "outputs", []) or []:
candidate = getattr(completion, "image", None)
if candidate is not None:
image = candidate
break
if image is not None:
break
if image is None:
print("No image found in outputs.")
return
out_path = args.output.resolve()
try:
image.save(out_path)
print(f"Saved image to {out_path}")
except Exception:
print("Generated output is not a PIL.Image; skipping save.")
if __name__ == "__main__":
main()
+28
View File
@@ -0,0 +1,28 @@
# Example config for running OmniLLM with local AR + DiT models.
stages:
- stage_id: 0
engine_type: AR
model_path: Qwen/Qwen3-0.6B
input_modalities: [text]
output_modalities: [text]
default_stage_args:
max_tokens: 16
temperature: 0.7
- stage_id: 1
engine_type: DiT
model_path: stabilityai/stable-diffusion-2-1
input_modalities: [text]
output_modalities: [image]
default_stage_args:
height: 512
width: 512
dit_config:
num_inference_steps: 20
guidance_scale: 5.0
use_diffusers: true
diffusers_pipeline: auto
height: 512
width: 512
device_config:
device: mps
dtype: float16
+1 -3
View File
@@ -34,6 +34,7 @@ dependencies = [
"numpy>=1.21.0",
"pillow>=8.0.0",
"opencv-python>=4.5.0",
"PyYAML>=6.0",
"gradio>=3.0.0",
"fastapi>=0.100.0",
"uvicorn>=0.20.0",
@@ -74,9 +75,6 @@ Documentation = "https://vllm-omni.readthedocs.io"
vllm = "vllm_omni.entrypoints.cli.main:main"
vllm-omni = "vllm_omni.entrypoints.cli.main:main"
[project.entry-points."vllm.plugins"]
omni = "vllm_omni.plugin:OmniPlugin"
[tool.setuptools.packages.find]
where = ["."]
include = ["vllm_omni*"]
+1
View File
@@ -5,6 +5,7 @@ transformers>=4.30.0
numpy>=1.21.0
pillow>=8.0.0
opencv-python>=4.5.0
PyYAML>=6.0
# API and serving
fastapi>=0.100.0
-83
View File
@@ -1,83 +0,0 @@
#!/bin/bash
# vLLM-omni Docker Setup Script for macOS
# This script helps set up Docker for vLLM-omni on macOS
set -e
echo "🐳 vLLM-omni Docker Setup for macOS"
echo "=================================="
# Check if Docker is installed
if ! command -v docker &> /dev/null; then
echo "❌ Docker is not installed. Please install Docker Desktop for Mac:"
echo " https://www.docker.com/products/docker-desktop/"
exit 1
fi
# Check if Docker is running
if ! docker info &> /dev/null; then
echo "❌ Docker is not running. Please start Docker Desktop."
exit 1
fi
echo "✅ Docker is installed and running"
# Check if NVIDIA Docker runtime is available (optional for macOS)
if docker info | grep -q nvidia; then
echo "✅ NVIDIA Docker runtime detected"
GPU_SUPPORT=true
else
echo "⚠️ NVIDIA Docker runtime not detected (normal on macOS)"
GPU_SUPPORT=false
fi
# Create necessary directories
echo "📁 Creating directories..."
mkdir -p models logs notebooks
# Build the Docker image
echo "🔨 Building vLLM-omni Docker image..."
docker build -f docker/Dockerfile.cpu -t vllm-omni-cpu:latest .
if [ $? -eq 0 ]; then
echo "✅ Docker image built successfully"
else
echo "❌ Failed to build Docker image"
exit 1
fi
# Test the image
echo "🧪 Testing Docker image..."
docker run --rm vllm-omni-cpu:latest python -c "import vllm_omni; print('vLLM-omni imported successfully')"
if [ $? -eq 0 ]; then
echo "✅ Docker image test passed"
else
echo "❌ Docker image test failed"
exit 1
fi
echo ""
echo "🎉 Setup complete! You can now use:"
echo ""
echo " # Start the server:"
echo " docker-compose -f docker/docker-compose.cpu.yml up vllm-omni-cpu"
echo ""
echo " # Start with Jupyter:"
echo " docker-compose -f docker/docker-compose.cpu.yml --profile jupyter up"
echo ""
echo " # Run a single command:"
echo " docker run --rm -p 8000:8000 vllm-omni-cpu:latest vllm serve Qwen/Qwen3-0.6B --omni --port 8000"
echo ""
echo " # Interactive shell:"
echo " docker run -it --rm vllm-omni-cpu:latest bash"
echo ""
# Optional: Start the server
read -p "🚀 Start the vLLM-omni server now? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Starting vLLM-omni server..."
docker-compose -f docker/docker-compose.cpu.yml up vllm-omni-cpu
fi
-147
View File
@@ -1,147 +0,0 @@
#!/usr/bin/env python3
"""
Model download script for vLLM-omni
Downloads commonly used AR and DiT models
"""
import os
import sys
import argparse
from pathlib import Path
def download_ar_models(models=None):
"""Download AR (Autoregressive) models"""
if models is None:
models = [
"Qwen/Qwen3-0.6B",
]
print("🚀 Downloading AR models...")
try:
from transformers import AutoTokenizer, AutoModel
except ImportError:
print("❌ transformers not installed. Installing...")
os.system("pip install transformers")
from transformers import AutoTokenizer, AutoModel
for model_name in models:
print(f"\n📥 Downloading AR model: {model_name}")
try:
print(f" - Downloading tokenizer...")
tokenizer = AutoTokenizer.from_pretrained(model_name)
print(f" - Downloading model...")
model = AutoModel.from_pretrained(model_name)
print(f"✅ {model_name} downloaded successfully")
# Print model info
total_params = sum(p.numel() for p in model.parameters())
size_mb = total_params * 4 / (1024 * 1024)
print(f" 📊 Parameters: {total_params:,} ({size_mb:.1f} MB)")
except Exception as e:
print(f"❌ Failed to download {model_name}: {e}")
def download_dit_models(models=None):
"""Download DiT (Diffusion Transformer) models"""
if models is None:
models = [
"stabilityai/stable-diffusion-2-1",
"runwayml/stable-diffusion-v1-5"
]
print("\n🚀 Downloading DiT models...")
try:
from diffusers import StableDiffusionPipeline
except ImportError:
print("❌ diffusers not installed. Installing...")
os.system("pip install diffusers")
from diffusers import StableDiffusionPipeline
for model_name in models:
print(f"\n📥 Downloading DiT model: {model_name}")
try:
print(f" - Downloading pipeline...")
pipe = StableDiffusionPipeline.from_pretrained(model_name)
print(f"✅ {model_name} downloaded successfully")
# Print model info
total_params = sum(p.numel() for p in pipe.unet.parameters())
size_mb = total_params * 4 / (1024 * 1024)
print(f" 📊 UNet parameters: {total_params:,} ({size_mb:.1f} MB)")
except Exception as e:
print(f"❌ Failed to download {model_name}: {e}")
def check_cache_directory():
"""Check and display cache directory info"""
cache_dir = Path.home() / ".cache" / "huggingface" / "hub"
print(f"📁 Cache directory: {cache_dir}")
if cache_dir.exists():
total_size = sum(f.stat().st_size for f in cache_dir.rglob('*') if f.is_file())
size_gb = total_size / (1024**3)
print(f"📊 Total cache size: {size_gb:.2f} GB")
# List downloaded models
model_dirs = [d for d in cache_dir.iterdir() if d.is_dir() and d.name.startswith("models--")]
if model_dirs:
print(f"📋 Downloaded models ({len(model_dirs)}):")
for model_dir in sorted(model_dirs):
model_name = model_dir.name.replace("models--", "").replace("--", "/")
print(f" - {model_name}")
else:
print("📁 Cache directory does not exist yet")
def clear_cache():
"""Clear model cache"""
cache_dir = Path.home() / ".cache" / "huggingface" / "hub"
if cache_dir.exists():
print(f"🗑️ Clearing cache directory: {cache_dir}")
import shutil
shutil.rmtree(cache_dir)
print("✅ Cache cleared successfully")
else:
print("📁 Cache directory does not exist")
def main():
parser = argparse.ArgumentParser(description="Download models for vLLM-omni")
parser.add_argument("--ar-models", nargs="+", help="AR models to download")
parser.add_argument("--dit-models", nargs="+", help="DiT models to download")
parser.add_argument("--all", action="store_true", help="Download all default models")
parser.add_argument("--check-cache", action="store_true", help="Check cache directory")
parser.add_argument("--clear-cache", action="store_true", help="Clear cache directory")
args = parser.parse_args()
print("🎯 vLLM-omni Model Downloader")
print("=" * 40)
if args.clear_cache:
clear_cache()
return
if args.check_cache:
check_cache_directory()
return
if args.all or args.ar_models:
download_ar_models(args.ar_models)
if args.all or args.dit_models:
download_dit_models(args.dit_models)
if not any([args.all, args.ar_models, args.dit_models, args.check_cache, args.clear_cache]):
print("❓ No action specified. Use --help for options.")
print("\nQuick start:")
print(" python scripts/download_models.py --all")
print(" python scripts/download_models.py --ar-models Qwen/Qwen3-0.6B")
print(" python scripts/download_models.py --check-cache")
if __name__ == "__main__":
main()
-82
View File
@@ -1,82 +0,0 @@
#!/bin/bash
# Quick vLLM-omni Test Script
# Fast test for basic functionality after changes
# Usage: ./scripts/quick_test.sh [port]
set -e
PORT=${1:-8000}
HOST="localhost"
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Cleanup
cleanup() {
log_info "Cleaning up..."
pkill -f "vllm serve" 2>/dev/null || true
}
trap cleanup EXIT
# Quick test
main() {
log_info "Quick vLLM-omni test on port $PORT"
# Test imports
log_info "Testing imports..."
python -c "import vllm_omni; from vllm_omni.entrypoints.omni_llm import OmniLLM, AsyncOmniLLM; print('✅ Imports OK')" || {
log_error "Import failed"
exit 1
}
# Start server
log_info "Starting server..."
vllm serve ./models/Qwen3-0.6B --omni --port $PORT > /dev/null 2>&1 &
SERVER_PID=$!
# Wait for server
sleep 15
# Test health with retry
log_info "Testing health endpoint..."
for i in {1..5}; do
if curl -s http://$HOST:$PORT/health | grep -q "healthy"; then
log_success "Health check passed"
break
else
if [ $i -eq 5 ]; then
log_error "Health check failed after 5 attempts"
exit 1
fi
log_info "Health check attempt $i failed, retrying in 3 seconds..."
sleep 3
fi
done
# Test generation
log_info "Testing text generation..."
response=$(curl -s -X POST http://$HOST:$PORT/generate \
-H "Content-Type: application/json" \
-d '{"prompts": ["Quick test"], "max_tokens": 10, "temperature": 0.7}')
if echo "$response" | python -c "import sys, json; data=json.load(sys.stdin); exit(0 if 'outputs' in data else 1)" 2>/dev/null; then
log_success "Generation test passed"
else
log_error "Generation test failed"
exit 1
fi
log_success "Quick test completed successfully!"
}
cd "$(dirname "$0")/.."
main "$@"
+74 -12
View File
@@ -122,16 +122,22 @@ start_server() {
# Test health endpoint
test_health() {
log_info "Testing health endpoint..."
local response=$(curl -s -w "%{http_code}" http://$HOST:$PORT/health 2>/dev/null || echo "000")
local http_code="${response: -3}"
local body="${response%???}"
if [ "$http_code" = "200" ]; then
log_success "Health check passed: $body"
else
log_error "Health check failed (HTTP $http_code): $body"
return 1
fi
for attempt in {1..10}; do
local response=$(curl -s -w "%{http_code}" http://$HOST:$PORT/health 2>/dev/null || echo "000")
local http_code="${response: -3}"
local body="${response%???}"
if [ "$http_code" = "200" ]; then
log_success "Health check passed: $body"
return 0
fi
log_warning "Health check attempt $attempt failed (HTTP $http_code). Retrying in 3s..."
sleep 3
done
log_error "Health check failed after multiple attempts"
return 1
}
# Test info endpoint
@@ -186,6 +192,60 @@ test_api_client() {
fi
}
# Test AR → DiT pipeline example
test_ar_dit_pipeline() {
log_info "Testing AR → DiT diffusers pipeline example..."
if ! python - 2>/dev/null <<'PY'
import importlib
for pkg in ("torch", "diffusers"):
if importlib.util.find_spec(pkg) is None:
raise ImportError(pkg)
PY
then
log_warning "Required packages for diffusers pipeline not found, skipping AR → DiT test"
return 0
fi
if [ ! -f "examples/omni/ar_dit_diffusers.py" ]; then
log_warning "AR → DiT example not found, skipping"
return 0
fi
local output_path="logs/ar_dit_pipeline.png"
local log_path="logs/ar_dit_pipeline.log"
rm -f "$output_path"
local cmd=(python examples/omni/ar_dit_diffusers.py
--prompt "Smoke test prompt of a lighthouse"
--seed 0
--output "$output_path")
local env_prefix=()
if [ -n "$AR_MODEL" ]; then
env_prefix+=("AR_MODEL=$AR_MODEL")
fi
if [ -n "$DIT_MODEL" ]; then
env_prefix+=("DIT_MODEL=$DIT_MODEL")
fi
if ! env "${env_prefix[@]}" "${cmd[@]}" >"$log_path" 2>&1; then
log_error "AR → DiT pipeline test failed"
log_info "AR → DiT logs:"
cat "$log_path"
return 1
fi
if [ ! -f "$output_path" ]; then
log_error "AR → DiT pipeline did not produce an image"
log_info "AR → DiT logs:"
cat "$log_path"
return 1
fi
log_success "AR → DiT pipeline test passed (output: $output_path)"
}
# Test simple usage example
test_simple_usage() {
log_info "Testing simple usage example..."
@@ -250,10 +310,11 @@ run_tests() {
test_generation
test_performance
test_api_client
test_ar_dit_pipeline
# Note: Simple usage test is commented out as it starts its own server
# test_simple_usage
log_success "All tests completed successfully!"
echo "=========================================="
log_info "Test Summary:"
@@ -262,6 +323,7 @@ run_tests() {
log_info "✅ Text generation functionality"
log_info "✅ Performance metrics"
log_info "✅ API client integration"
log_info "✅ AR → DiT diffusers pipeline"
log_info "✅ All imports working correctly"
}
+50 -37
View File
@@ -3,25 +3,16 @@ Stage configuration for vLLM-omni multi-stage processing.
"""
from dataclasses import dataclass
from typing import List, Optional, Type, Literal, Any
from vllm.config import VllmConfig
from typing import List, Optional, Type, Literal, Any, Dict
from vllm.config import (
VllmConfig,
DeviceConfig,
LoadConfig,
ModelConfig,
CompilationConfig,
)
from vllm.executor.executor_base import ExecutorBase as Executor
@dataclass
class DiTConfig:
"""Configuration for DiT (Diffusion Transformer) stages."""
model_type: str
scheduler_type: str
num_inference_steps: int
guidance_scale: float = 7.5
use_diffusers: bool = False
diffusers_pipeline: Optional[str] = None
height: int = 512
width: int = 512
batch_size: int = 1
@dataclass
class DiTCacheTensor:
"""Configuration for DiT cache tensors."""
@@ -41,18 +32,46 @@ class DiTCacheConfig:
cache_compression: bool = False
@dataclass
class DiTConfig:
"""Configuration for DiT (Diffusion Transformer) stages."""
num_inference_steps: int
"""Number of diffusion inference steps."""
model_config: Optional[ModelConfig] = None
"""Model configuration."""
scheduler_config: Optional[Any] = None
"""Scheduler configuration."""
device_config: Optional[DeviceConfig] = None
"""Device configuration."""
load_config: Optional[LoadConfig] = None
"""Model loading configuration."""
compilation_config: Optional[CompilationConfig] = None
"""Compilation configuration."""
dit_cache_config: Optional[DiTCacheConfig] = None
"""DiT cache configuration."""
guidance_scale: float = 7.5
use_diffusers: bool = False
diffusers_pipeline: Optional[str] = None
height: int = 512
width: int = 512
batch_size: int = 1
@dataclass
class OmniStageConfig:
"""Configuration for a processing stage in vLLM-omni."""
stage_id: int
engine_type: Literal["AR", "DiT"]
model_path: str
input_modalities: List[str]
output_modalities: List[str]
vllm_config: Optional[VllmConfig] = None
executor_class: Type[Executor] = None # Will be set based on engine_type
dit_config: Optional[DiTConfig] = None
cache_config: Optional[DiTCacheConfig] = None
executor_class: Type[Executor] = None # Will be set based on engine_type
default_stage_args: Optional[Dict[str, Any]] = None
stage_output: Optional[Any] = None
def __post_init__(self):
@@ -94,7 +113,8 @@ def create_ar_stage_config(
input_modalities: List[str] = None,
output_modalities: List[str] = None,
vllm_config: Optional[VllmConfig] = None,
executor_class: Optional[Type[Executor]] = None
executor_class: Optional[Type[Executor]] = None,
default_stage_args: Optional[Dict[str, Any]] = None,
) -> OmniStageConfig:
"""Create a configuration for an AR (Autoregressive) stage."""
if input_modalities is None:
@@ -111,19 +131,19 @@ def create_ar_stage_config(
input_modalities=input_modalities,
output_modalities=output_modalities,
vllm_config=vllm_config,
executor_class=executor_class
executor_class=executor_class,
default_stage_args=default_stage_args,
)
def create_dit_stage_config(
stage_id: int,
model_path: str,
input_modalities: List[str] = None,
output_modalities: List[str] = None,
input_modalities: Optional[List[str]] = None,
output_modalities: Optional[List[str]] = None,
dit_config: Optional[DiTConfig] = None,
cache_config: Optional[DiTCacheConfig] = None,
vllm_config: Optional[VllmConfig] = None,
executor_class: Optional[Type[Executor]] = None
executor_class: Optional[Type[Executor]] = None,
default_stage_args: Optional[Dict[str, Any]] = None,
) -> OmniStageConfig:
"""Create a configuration for a DiT (Diffusion Transformer) stage."""
if input_modalities is None:
@@ -132,22 +152,15 @@ def create_dit_stage_config(
output_modalities = ["image"]
if dit_config is None:
dit_config = DiTConfig(
model_type="dit",
scheduler_type="ddpm",
num_inference_steps=50
)
# For now, we'll create minimal configs
# vllm_config and executor_class will be handled by the LLM class
dit_config = DiTConfig(num_inference_steps=50)
return OmniStageConfig(
stage_id=stage_id,
engine_type="DiT",
model_path=model_path,
input_modalities=input_modalities,
output_modalities=output_modalities,
vllm_config=vllm_config,
dit_config=dit_config,
cache_config=cache_config,
executor_class=executor_class
executor_class=executor_class,
default_stage_args=default_stage_args,
)
-1
View File
@@ -1 +0,0 @@
from vllm.v1.core.sched import SchedulerInterface
-229
View File
@@ -1,229 +0,0 @@
"""
Diffusion scheduler for DiT (Diffusion Transformer) models in vLLM-omni.
"""
from typing import Dict, List, Optional, Any, TYPE_CHECKING
from vllm.v1.core.sched.scheduler import SchedulerInterface
from vllm.config import VllmConfig, KVCacheConfig
from vllm.v1.core.sched.structured_output_manager import StructuredOutputManager
from vllm.v1.core.sched.mm_registry import MultiModalRegistry, MULTIMODAL_REGISTRY
from ..dit_cache_manager import DiTCacheManager
from ...config import DiTCacheConfig
if TYPE_CHECKING:
from vllm.v1.core.sched.scheduler import SchedulerOutput
class OmniDiffusionScheduler(SchedulerInterface):
"""Scheduler for DiT models with caching optimization."""
def __init__(
self,
vllm_config: VllmConfig,
kv_cache_config: KVCacheConfig,
dit_cache_config: DiTCacheConfig,
structured_output_manager: StructuredOutputManager,
mm_registry: MultiModalRegistry = MULTIMODAL_REGISTRY,
include_finished_set: bool = False,
log_stats: bool = False,
) -> None:
super().__init__(
vllm_config=vllm_config,
kv_cache_config=kv_cache_config,
structured_output_manager=structured_output_manager,
mm_registry=mm_registry,
include_finished_set=include_finished_set,
log_stats=log_stats,
)
self.dit_cache_config = dit_cache_config
self.dit_cache_manager = DiTCacheManager(dit_cache_config)
# DiT-specific scheduling state
self.dit_requests: Dict[str, Dict[str, Any]] = {}
self.current_step: int = 0
self.max_steps: int = 50 # Default, will be updated from config
def schedule(self) -> "SchedulerOutput":
"""Schedule DiT requests with caching optimization."""
# Get pending requests
pending_requests = self._get_pending_requests()
if not pending_requests:
return self._create_empty_scheduler_output()
# Apply DiT-specific scheduling logic
scheduled_requests = self._schedule_dit_requests(pending_requests)
# Create scheduler output
return self._create_scheduler_output(scheduled_requests)
def _get_pending_requests(self) -> List[Dict[str, Any]]:
"""Get pending DiT requests."""
# This would integrate with vLLM's request management system
# For now, we'll return a mock implementation
return []
def _schedule_dit_requests(self, requests: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Apply DiT-specific scheduling logic."""
scheduled_requests = []
for request in requests:
# Check cache for this request
cached_result = self.dit_cache_manager.get_cache(request['request_id'])
if cached_result is not None:
# Use cached result
request['cached'] = True
request['cached_result'] = cached_result
else:
# Allocate cache for new request
cache_tensor = self.dit_cache_manager.allocate_cache(
request['request_id'],
request.get('cache_size', 1024)
)
request['cache_tensor'] = cache_tensor
request['cached'] = False
# Apply DiT-specific scheduling policies
request = self._apply_dit_scheduling_policies(request)
scheduled_requests.append(request)
return scheduled_requests
def _apply_dit_scheduling_policies(self, request: Dict[str, Any]) -> Dict[str, Any]:
"""Apply DiT-specific scheduling policies."""
# For now, we'll use a simple FIFO policy
# In practice, this could include:
# - Priority-based scheduling
# - Batch size optimization
# - Memory-aware scheduling
# - Quality vs speed trade-offs
request['priority'] = request.get('priority', 0)
request['batch_size'] = request.get('batch_size', 1)
request['scheduling_policy'] = 'fifo'
return request
def _create_empty_scheduler_output(self) -> "SchedulerOutput":
"""Create an empty scheduler output."""
from vllm.v1.core.sched.scheduler import SchedulerOutput
return SchedulerOutput(
scheduled_seq_groups=[],
ignored_seq_groups=[],
preempted_seq_groups=[],
num_preemption_groups=0,
num_batched_tokens=0,
blocks_to_swap_in={},
blocks_to_swap_out={},
blocks_to_copy={},
ignored_seq_groups=[],
num_ignored_seq_groups=0,
)
def _create_scheduler_output(self, scheduled_requests: List[Dict[str, Any]]) -> "SchedulerOutput":
"""Create scheduler output from scheduled requests."""
from vllm.v1.core.sched.scheduler import SchedulerOutput
# Convert scheduled requests to vLLM's expected format
scheduled_seq_groups = []
for request in scheduled_requests:
seq_group = self._create_seq_group_from_request(request)
scheduled_seq_groups.append(seq_group)
return SchedulerOutput(
scheduled_seq_groups=scheduled_seq_groups,
ignored_seq_groups=[],
preempted_seq_groups=[],
num_preemption_groups=0,
num_batched_tokens=sum(req.get('num_tokens', 0) for req in scheduled_requests),
blocks_to_swap_in={},
blocks_to_swap_out={},
blocks_to_copy={},
ignored_seq_groups=[],
num_ignored_seq_groups=0,
)
def _create_seq_group_from_request(self, request: Dict[str, Any]) -> Any:
"""Create a sequence group from a DiT request."""
# This would create a proper sequence group
# For now, we'll return a mock implementation
from vllm.v1.core.sched.sequence import SequenceGroup
# Mock sequence group creation
# In practice, this would properly create a SequenceGroup
# with the appropriate metadata for DiT processing
return None
def update_from_output(
self,
scheduler_output: "SchedulerOutput",
model_output: Any,
) -> List[Any]:
"""Update scheduler state from model output."""
# Update cache with new results
for seq_group in scheduler_output.scheduled_seq_groups:
if hasattr(seq_group, 'request_id'):
# Store result in cache
self.dit_cache_manager.store_cache(
seq_group.request_id,
model_output
)
# Update DiT-specific state
self.current_step += 1
# Call parent update method
return super().update_from_output(scheduler_output, model_output)
def add_request(self, request_id: str, **kwargs) -> None:
"""Add a new DiT request to the scheduler."""
# Store request metadata
self.dit_requests[request_id] = {
'request_id': request_id,
'added_time': self.current_step,
'status': 'pending',
**kwargs
}
# Call parent add_request method
super().add_request(request_id, **kwargs)
def remove_request(self, request_id: str) -> None:
"""Remove a DiT request from the scheduler."""
# Clean up request metadata
if request_id in self.dit_requests:
del self.dit_requests[request_id]
# Release cache for this request
self.dit_cache_manager.release_cache(request_id)
# Call parent remove_request method
super().remove_request(request_id)
def get_dit_request_info(self, request_id: str) -> Optional[Dict[str, Any]]:
"""Get information about a specific DiT request."""
return self.dit_requests.get(request_id)
def get_cache_statistics(self) -> Dict[str, Any]:
"""Get cache statistics for monitoring."""
return self.dit_cache_manager.get_statistics()
def clear_expired_cache(self) -> None:
"""Clear expired cache entries."""
self.dit_cache_manager.clear_expired_cache()
def set_max_steps(self, max_steps: int) -> None:
"""Set the maximum number of diffusion steps."""
self.max_steps = max_steps
def get_current_step(self) -> int:
"""Get the current diffusion step."""
return self.current_step
def reset_step_counter(self) -> None:
"""Reset the diffusion step counter."""
self.current_step = 0
+3 -2
View File
@@ -2,9 +2,10 @@
Stage manager for orchestrating multiple engines in vLLM-omni.
"""
from typing import List, Optional
from typing import List, Optional, Union
from vllm.v1.engine.llm_engine import LLMEngine
from vllm.v1.engine.async_llm import AsyncLLM
from ..engine.diffusion_engine import DiffusionEngine
from ..config import OmniStageConfig
@@ -15,7 +16,7 @@ class StageManager:
def __init__(self, stage_configs: List[OmniStageConfig], log_stats: bool = False):
self.stage_configs = stage_configs
self.log_stats = log_stats
self.engine_list: List[LLMEngine] = []
self.engine_list: List[Union[LLMEngine, DiffusionEngine]] = []
self.async_engine_list: List[AsyncLLM] = []
self._initialized = False
self._async_initialized = False
+132
View File
@@ -0,0 +1,132 @@
from typing import Any, Optional
from ..config import DiTConfig
from ..worker.gpu_diffusion_worker import (
DiffusionGPUWorker,
DiffusionRunnerOutput,
)
class DiffusionEngine:
"Legacy placeholder for DiffusionEngine"
def __init__(
self,
dit_config: DiTConfig,
model_path: Optional[str] = None,
log_stats: bool = False,
multiprocess_mode: bool = False,
) -> None:
self.dit_config = dit_config
model_cfg = getattr(dit_config, "model_config", None)
config_model_path = None
if model_cfg is not None:
if isinstance(model_cfg, dict):
config_model_path = model_cfg.get("model")
else:
config_model_path = getattr(model_cfg, "model", None)
self.model_path = model_path or config_model_path
self.model_config = dit_config.model_config
self.cache_config = dit_config.dit_cache_config
self.height = dit_config.height
self.width = dit_config.width
self.num_inference_steps = dit_config.num_inference_steps
self.guidance_scale = dit_config.guidance_scale
self.log_stats = log_stats
self.multiprocess_mode = multiprocess_mode
def load_model(self):
raise NotImplementedError
def generate(self, *args, **kwargs):
"""Generate images based on the input prompt."""
# Placeholder implementation
# In a full implementation, this would use the loaded model to generate images
raise NotImplementedError("This method should be implemented in subclasses.")
class DiffusersPipelineEngine(DiffusionEngine):
"""Thin executor that delegates to a diffusion worker."""
# crucial: must match signature of parent class
def __init__(
self,
dit_config: DiTConfig,
model_path: Optional[str] = None,
log_stats: bool = False,
multiprocess_mode: bool = False,
) -> None:
super().__init__(dit_config, model_path, log_stats, multiprocess_mode)
resolved_model_path = self.model_path or None
if resolved_model_path is None:
raise ValueError("Model path must be provided for DiffusersPipelineEngine.")
device_cfg = getattr(dit_config, "device_config", None)
model_cfg = getattr(dit_config, "model_config", None)
device = None
dtype = None
if device_cfg:
if isinstance(device_cfg, dict):
device = device_cfg.get("device")
dtype = device_cfg.get("dtype", None)
else:
device = getattr(device_cfg, "device", None)
dtype = getattr(device_cfg, "dtype", None)
if dtype is None and model_cfg:
if isinstance(model_cfg, dict):
dtype = model_cfg.get("dtype")
else:
dtype = getattr(model_cfg, "dtype", None)
self.worker = DiffusionGPUWorker(
resolved_model_path,
pipeline_name=dit_config.diffusers_pipeline,
device=device,
dtype=dtype,
)
def generate(
self,
prompt: str,
*,
height: Optional[int] = None,
width: Optional[int] = None,
num_inference_steps: Optional[int] = None,
guidance_scale: Optional[float] = None,
negative_prompt: Optional[str] = None,
seed: Optional[int] = None,
image: Optional[Any] = None,
) -> DiffusionRunnerOutput:
height = int(height) if height is not None else self.height
width = int(width) if width is not None else self.width
num_steps = (
int(num_inference_steps)
if num_inference_steps is not None
else self.num_inference_steps
)
guidance = (
float(guidance_scale)
if guidance_scale is not None
else self.guidance_scale
)
return self.worker.generate(
prompt=prompt,
height=height,
width=width,
num_inference_steps=num_steps,
guidance_scale=guidance,
negative_prompt=negative_prompt,
seed=seed,
image=image,
)
+31 -47
View File
@@ -37,7 +37,23 @@ class MultimodalOutputProcessor:
handler = self.output_handlers.get(output_type, self._process_pooling_output)
return handler(engine_core_output)
def _build_request_output(
self,
source: Any,
completion_outputs: List[CompletionOutput],
) -> RequestOutput:
"""Helper to construct RequestOutput with safe defaults."""
return RequestOutput(
request_id=getattr(source, "request_id", "unknown"),
prompt=getattr(source, "prompt", ""),
prompt_token_ids=getattr(source, "prompt_token_ids", []),
prompt_logprobs=getattr(source, "prompt_logprobs", None),
outputs=completion_outputs,
finished=getattr(source, "finished", True),
)
def _detect_output_type(self, output: Any) -> str:
"""Detect the type of output based on its content."""
if hasattr(output, 'output_type'):
@@ -76,16 +92,8 @@ class MultimodalOutputProcessor:
finish_reason=getattr(output, 'finish_reason', 'length')
)
request_output = RequestOutput(
request_id=getattr(output, 'request_id', 'unknown'),
prompt=getattr(output, 'prompt', ''),
prompt_token_ids=getattr(output, 'prompt_token_ids', []),
outputs=[completion_output],
finished=getattr(output, 'finished', True)
)
return [request_output]
return [self._build_request_output(output, [completion_output])]
def _process_image_output(self, output: Any) -> List[RequestOutput]:
"""Process image output."""
# For image outputs, we need to create a special RequestOutput
@@ -109,15 +117,9 @@ class MultimodalOutputProcessor:
# Add image data to the completion output
completion_output.image = image_data
request_output = RequestOutput(
request_id=getattr(output, 'request_id', 'unknown'),
prompt=getattr(output, 'prompt', ''),
prompt_token_ids=getattr(output, 'prompt_token_ids', []),
outputs=[completion_output],
finished=getattr(output, 'finished', True)
)
return [request_output]
return [
self._build_request_output(output, [completion_output])
]
def _process_text_image_output(self, output: Any) -> List[RequestOutput]:
"""Process combined text and image output."""
@@ -141,15 +143,9 @@ class MultimodalOutputProcessor:
# Add image data to the completion output
completion_output.image = image_data
request_output = RequestOutput(
request_id=getattr(output, 'request_id', 'unknown'),
prompt=getattr(output, 'prompt', ''),
prompt_token_ids=getattr(output, 'prompt_token_ids', []),
outputs=[completion_output],
finished=getattr(output, 'finished', True)
)
return [request_output]
return [
self._build_request_output(output, [completion_output])
]
def _process_latents_output(self, output: Any) -> List[RequestOutput]:
"""Process latent representation output."""
@@ -171,15 +167,9 @@ class MultimodalOutputProcessor:
# Add latent data to the completion output
completion_output.latents = latent_data
request_output = RequestOutput(
request_id=getattr(output, 'request_id', 'unknown'),
prompt=getattr(output, 'prompt', ''),
prompt_token_ids=getattr(output, 'prompt_token_ids', []),
outputs=[completion_output],
finished=getattr(output, 'finished', True)
)
return [request_output]
return [
self._build_request_output(output, [completion_output])
]
def _process_pooling_output(self, output: Any) -> List[RequestOutput]:
"""Process pooling output (hidden states, embeddings, etc.)."""
@@ -201,15 +191,9 @@ class MultimodalOutputProcessor:
# Add pooling data to the completion output
completion_output.pooler_output = pooling_data
request_output = RequestOutput(
request_id=getattr(output, 'request_id', 'unknown'),
prompt=getattr(output, 'prompt', ''),
prompt_token_ids=getattr(output, 'prompt_token_ids', []),
outputs=[completion_output],
finished=getattr(output, 'finished', True)
)
return [request_output]
return [
self._build_request_output(output, [completion_output])
]
def process_outputs(self, engine_core_outputs: List[EngineCoreOutput], **kwargs) -> List[RequestOutput]:
"""Process multiple engine core outputs."""
+10 -7
View File
@@ -5,8 +5,13 @@ Omni serve command for vLLM-omni.
import argparse
import asyncio
from typing import List, Optional
from .omni_llm import AsyncOmniLLM
from vllm_omni.config import create_ar_stage_config, create_dit_stage_config, DiTConfig
from vllm_omni.config import (
DiTConfig,
create_ar_stage_config,
create_dit_stage_config,
)
class OmniServeCommand:
@@ -129,11 +134,10 @@ class OmniServeCommand:
if dit_model:
dit_config = DiTConfig(
model_type="dit",
scheduler_type="ddpm",
num_inference_steps=args.dit_steps,
guidance_scale=args.dit_guidance_scale,
use_diffusers=args.use_diffusers
use_diffusers=args.use_diffusers,
diffusers_pipeline="auto" if args.use_diffusers else None,
)
dit_stage_config = create_dit_stage_config(
@@ -160,11 +164,10 @@ class OmniServeCommand:
stage_configs.append(ar_config)
dit_config = DiTConfig(
model_type="dit",
scheduler_type="ddpm",
num_inference_steps=args.dit_steps,
guidance_scale=args.dit_guidance_scale,
use_diffusers=args.use_diffusers
use_diffusers=args.use_diffusers,
diffusers_pipeline="auto" if args.use_diffusers else None,
)
dit_stage_config = create_dit_stage_config(
+241 -69
View File
@@ -8,10 +8,10 @@ from vllm.v1.engine.async_llm import AsyncLLM
from vllm.v1.engine.llm_engine import LLMEngine
from vllm.outputs import RequestOutput, LoRARequest
from ..config import OmniStageConfig
from ..core.stage_manager import StageManager
from ..engine.output_processor import MultimodalOutputProcessor
from ..engine.diffusion_engine import DiffusersPipelineEngine
class OmniLLM(LLM):
@@ -25,7 +25,10 @@ class OmniLLM(LLM):
):
# Use the first stage's model as the default model for LLM
default_model = stage_configs[0].model_path if stage_configs else "test-model"
# Track whether we should launch engines in multiprocess mode.
self.multiprocess_mode = kwargs.pop("multiprocess_mode", False)
# Fix configuration validation issues
# Ensure max_num_batched_tokens is at least as large as max_model_len
if 'max_model_len' in kwargs and 'max_num_batched_tokens' in kwargs:
@@ -52,14 +55,24 @@ class OmniLLM(LLM):
def generate(
self,
stage_args_list: List[Dict[str, Any]],
stage_args_list: Optional[List[Dict[str, Any]]] = None,
use_tqdm: Union[bool, Callable[..., Any]] = True,
lora_request: Optional[Union[List[LoRARequest], LoRARequest]] = None,
priority: Optional[List[int]] = None,
**kwargs
*,
prompt: Optional[str] = None,
stage_overrides: Optional[Dict[int, Dict[str, Any]]] = None,
) -> List[RequestOutput]:
"""Main generation interface - orchestrates multi-stage processing."""
if stage_args_list is None:
if prompt is None:
raise ValueError(
"prompt must be provided when stage_args_list is not supplied"
)
stage_args_list = self._build_stage_args_from_config(
prompt, stage_overrides or {}
)
if len(stage_args_list) != len(self.stage_configs):
raise ValueError(
f"Number of stage arguments ({len(stage_args_list)}) must match "
@@ -74,12 +87,12 @@ class OmniLLM(LLM):
# Prepare input for this stage
processed_input = self._process_stage_inputs(
stage_config, stage_args, current_output
stage_config, stage_args or {}, current_output
)
# Execute stage
stage_output = self._execute_stage(
stage_engine, processed_input, lora_request, priority
stage_engine, processed_input, lora_request, priority, stage_config
)
# Update for next stage
@@ -90,6 +103,23 @@ class OmniLLM(LLM):
final_output = self.output_processor.process_output(current_output)
return final_output
def _build_stage_args_from_config(
self,
prompt: str,
stage_overrides: Dict[int, Dict[str, Any]],
) -> List[Dict[str, Any]]:
"""Derive per-stage argument dictionaries from configuration defaults."""
stage_args: List[Dict[str, Any]] = []
for stage_config in self.stage_configs:
combined: Dict[str, Any] = dict(stage_config.default_stage_args or {})
override = stage_overrides.get(stage_config.stage_id)
if override:
combined.update(override)
if stage_config.engine_type == "AR":
combined["prompt"] = prompt
stage_args.append(combined)
return stage_args
def _process_stage_inputs(
self,
stage_config: OmniStageConfig,
@@ -98,24 +128,24 @@ class OmniLLM(LLM):
) -> Dict[str, Any]:
"""Prepare input for specific stage."""
if stage_config.engine_type == "AR":
return self._process_ar_inputs(stage_args, previous_output)
return self._process_ar_inputs(stage_config, stage_args, previous_output)
elif stage_config.engine_type == "DiT":
return self._process_dit_inputs(stage_args, previous_output)
return self._process_dit_inputs(stage_config, stage_args, previous_output)
else:
raise NotImplementedError(f"Unknown engine type: {stage_config.engine_type}")
def _process_ar_inputs(
self,
stage_config: OmniStageConfig,
stage_args: Dict[str, Any],
previous_output: Optional[Any]
) -> Dict[str, Any]:
"""Process inputs for AR stage."""
# For AR stages, we typically use text prompts
processed_input = {
"prompt": stage_args.get("prompt", ""),
"max_tokens": stage_args.get("max_tokens", 100),
"temperature": stage_args.get("temperature", 0.7),
}
combined = dict(stage_config.default_stage_args or {})
combined.update(stage_args)
combined.setdefault("prompt", "")
combined.setdefault("max_tokens", 100)
combined.setdefault("temperature", 0.7)
# If we have previous output (e.g., from a previous AR stage),
# we might want to use it as context
@@ -124,49 +154,97 @@ class OmniLLM(LLM):
if hasattr(previous_output, 'outputs') and previous_output.outputs:
last_output = previous_output.outputs[-1]
if hasattr(last_output, 'text'):
processed_input["prompt"] = last_output.text + " " + processed_input["prompt"]
return processed_input
combined["prompt"] = last_output.text + " " + combined["prompt"]
return combined
def _process_dit_inputs(
self,
stage_config: OmniStageConfig,
stage_args: Dict[str, Any],
previous_output: Optional[Any]
) -> Dict[str, Any]:
"""Process inputs for DiT stage."""
processed_input = {
"prompt": stage_args.get("prompt", ""),
"height": stage_args.get("height", 512),
"width": stage_args.get("width", 512),
"num_inference_steps": stage_args.get("num_inference_steps", 50),
"guidance_scale": stage_args.get("guidance_scale", 7.5),
}
combined = dict(stage_config.default_stage_args or {})
combined.update(stage_args)
dit = stage_config.dit_config
if dit is not None:
combined.setdefault("height", getattr(dit, "height", 512))
combined.setdefault("width", getattr(dit, "width", 512))
combined.setdefault(
"num_inference_steps", getattr(dit, "num_inference_steps", 50)
)
combined.setdefault(
"guidance_scale", getattr(dit, "guidance_scale", 7.5)
)
else:
combined.setdefault("height", 512)
combined.setdefault("width", 512)
combined.setdefault("num_inference_steps", 50)
combined.setdefault("guidance_scale", 7.5)
# Handle image inputs if present
if "image" in stage_args:
# For now, we'll pass the image path directly
# In a full implementation, this would involve VAE encoding
processed_input["image"] = stage_args["image"]
combined["image"] = stage_args["image"]
# If we have previous output from an AR stage, we might want to use it
if previous_output is not None:
# Extract text from previous AR output
if hasattr(previous_output, 'outputs') and previous_output.outputs:
last_output = previous_output.outputs[-1]
if hasattr(last_output, 'text'):
processed_input["prompt"] = last_output.text
return processed_input
combined["prompt"] = last_output.text
combined.setdefault("prompt", stage_args.get("prompt", ""))
return combined
def _execute_stage(
self,
stage_engine: LLMEngine,
stage_engine: Optional[Union[LLMEngine, DiffusersPipelineEngine]],
processed_input: Dict[str, Any],
lora_request: Optional[Union[List[LoRARequest], LoRARequest]] = None,
priority: Optional[List[int]] = None
priority: Optional[List[int]] = None,
stage_config: Optional[OmniStageConfig] = None,
) -> Any:
"""Execute a single stage."""
# Use the parent LLM's generate method for actual text generation
# DiT via diffusers backend
if stage_config and stage_config.engine_type == "DiT":
dit = stage_config.dit_config
if dit and getattr(dit, "use_diffusers", False):
# Lazy-init executor per stage
if not hasattr(self, "_dit_engines"):
self._dit_engines = {}
exec_inst = self._dit_engines.get(stage_config.stage_id)
if exec_inst is None:
exec_inst = DiffusersPipelineEngine(
dit_config=dit,
model_path=stage_config.model_path,
log_stats=self.log_stats,
multiprocess_mode=self.multiprocess_mode,
)
self._dit_engines[stage_config.stage_id] = exec_inst
return exec_inst.generate(
prompt=processed_input.get("prompt", ""),
height=processed_input.get("height", getattr(dit, "height", 512)),
width=processed_input.get("width", getattr(dit, "width", 512)),
num_inference_steps=processed_input.get(
"num_inference_steps", getattr(dit, "num_inference_steps", 30)
),
guidance_scale=processed_input.get(
"guidance_scale", getattr(dit, "guidance_scale", 5.0)
),
negative_prompt=processed_input.get("negative_prompt"),
seed=processed_input.get("seed"),
image=processed_input.get("image"),
)
# Use the parent LLM's generate method for AR text generation
prompt = processed_input.get("prompt", "")
max_tokens = processed_input.get("max_tokens", 100)
temperature = processed_input.get("temperature", 0.7)
@@ -252,14 +330,25 @@ class AsyncOmniLLM(LLM):
async def generate_async(
self,
stage_args_list: List[Dict[str, Any]],
stage_args_list: Optional[List[Dict[str, Any]]] = None,
use_tqdm: Union[bool, Callable[..., Any]] = True,
lora_request: Optional[Union[List[LoRARequest], LoRARequest]] = None,
priority: Optional[List[int]] = None,
**kwargs
*,
prompt: Optional[str] = None,
stage_overrides: Optional[Dict[int, Dict[str, Any]]] = None,
) -> List[RequestOutput]:
"""Async generation interface - orchestrates multi-stage processing."""
if stage_args_list is None:
if prompt is None:
raise ValueError(
"prompt must be provided when stage_args_list is not supplied"
)
stage_args_list = self._build_stage_args_from_config(
prompt, stage_overrides or {}
)
if len(stage_args_list) != len(self.stage_configs):
raise ValueError(
f"Number of stage arguments ({len(stage_args_list)}) must match "
@@ -274,12 +363,12 @@ class AsyncOmniLLM(LLM):
# Prepare input for this stage
processed_input = self._process_stage_inputs(
stage_config, stage_args, current_output
stage_config, stage_args or {}, current_output
)
# Execute stage asynchronously
stage_output = await self._execute_stage_async(
stage_engine, processed_input, lora_request, priority
stage_engine, processed_input, lora_request, priority, stage_config
)
# Update for next stage
@@ -290,6 +379,22 @@ class AsyncOmniLLM(LLM):
final_output = self.output_processor.process_output(current_output)
return final_output
def _build_stage_args_from_config(
self,
prompt: str,
stage_overrides: Dict[int, Dict[str, Any]],
) -> List[Dict[str, Any]]:
stage_args: List[Dict[str, Any]] = []
for stage_config in self.stage_configs:
combined: Dict[str, Any] = dict(stage_config.default_stage_args or {})
override = stage_overrides.get(stage_config.stage_id)
if override:
combined.update(override)
if stage_config.engine_type == "AR":
combined["prompt"] = prompt
stage_args.append(combined)
return stage_args
def _process_stage_inputs(
self,
stage_config: OmniStageConfig,
@@ -298,66 +403,133 @@ class AsyncOmniLLM(LLM):
) -> Dict[str, Any]:
"""Prepare input for specific stage (same as OmniLLM)."""
if stage_config.engine_type == "AR":
return self._process_ar_inputs(stage_args, previous_output)
return self._process_ar_inputs(stage_config, stage_args, previous_output)
elif stage_config.engine_type == "DiT":
return self._process_dit_inputs(stage_args, previous_output)
return self._process_dit_inputs(stage_config, stage_args, previous_output)
else:
raise NotImplementedError(f"Unknown engine type: {stage_config.engine_type}")
def _process_ar_inputs(
self,
stage_config: OmniStageConfig,
stage_args: Dict[str, Any],
previous_output: Optional[Any]
) -> Dict[str, Any]:
"""Process inputs for AR stage (same as OmniLLM)."""
processed_input = {
"prompt": stage_args.get("prompt", ""),
"max_tokens": stage_args.get("max_tokens", 100),
"temperature": stage_args.get("temperature", 0.7),
}
combined = dict(stage_config.default_stage_args or {})
combined.update(stage_args)
combined.setdefault("prompt", "")
combined.setdefault("max_tokens", 100)
combined.setdefault("temperature", 0.7)
if previous_output is not None:
if hasattr(previous_output, 'outputs') and previous_output.outputs:
last_output = previous_output.outputs[-1]
if hasattr(last_output, 'text'):
processed_input["prompt"] = last_output.text + " " + processed_input["prompt"]
return processed_input
combined["prompt"] = last_output.text + " " + combined["prompt"]
return combined
def _process_dit_inputs(
self,
stage_config: OmniStageConfig,
stage_args: Dict[str, Any],
previous_output: Optional[Any]
) -> Dict[str, Any]:
"""Process inputs for DiT stage (same as OmniLLM)."""
processed_input = {
"prompt": stage_args.get("prompt", ""),
"height": stage_args.get("height", 512),
"width": stage_args.get("width", 512),
"num_inference_steps": stage_args.get("num_inference_steps", 50),
"guidance_scale": stage_args.get("guidance_scale", 7.5),
}
combined = dict(stage_config.default_stage_args or {})
combined.update(stage_args)
dit = stage_config.dit_config
if dit is not None:
combined.setdefault("height", getattr(dit, "height", 512))
combined.setdefault("width", getattr(dit, "width", 512))
combined.setdefault(
"num_inference_steps", getattr(dit, "num_inference_steps", 50)
)
combined.setdefault(
"guidance_scale", getattr(dit, "guidance_scale", 7.5)
)
else:
combined.setdefault("height", 512)
combined.setdefault("width", 512)
combined.setdefault("num_inference_steps", 50)
combined.setdefault("guidance_scale", 7.5)
if "image" in stage_args:
processed_input["image"] = stage_args["image"]
combined["image"] = stage_args["image"]
if previous_output is not None:
if hasattr(previous_output, 'outputs') and previous_output.outputs:
last_output = previous_output.outputs[-1]
if hasattr(last_output, 'text'):
processed_input["prompt"] = last_output.text
return processed_input
combined["prompt"] = last_output.text
combined.setdefault("prompt", stage_args.get("prompt", ""))
return combined
async def _execute_stage_async(
self,
stage_engine: AsyncLLM,
processed_input: Dict[str, Any],
lora_request: Optional[Union[List[LoRARequest], LoRARequest]] = None,
priority: Optional[List[int]] = None
priority: Optional[List[int]] = None,
stage_config: Optional[OmniStageConfig] = None,
) -> Any:
"""Execute a single stage asynchronously."""
# Use the parent LLM's generate method for actual text generation
# DiT via diffusers backend (sync call inside async for MVP)
if stage_config and stage_config.engine_type == "DiT":
dit = stage_config.dit_config
if dit and getattr(dit, "use_diffusers", False):
if not hasattr(self, "_dit_executors"):
self._dit_executors = {}
exec_inst = self._dit_executors.get(stage_config.stage_id)
if exec_inst is None:
from vllm_omni.executor.diffusers_executor import (
DiffusersPipelineExecutor,
)
pipeline_name = getattr(dit, "diffusers_pipeline", None)
device_cfg = getattr(dit, "device_config", None)
model_cfg = getattr(dit, "model_config", None)
if isinstance(device_cfg, dict):
device = device_cfg.get("device")
dtype = device_cfg.get("dtype")
else:
device = getattr(device_cfg, "device", None)
dtype = getattr(device_cfg, "dtype", None)
if dtype is None:
if isinstance(model_cfg, dict):
dtype = model_cfg.get("dtype")
else:
dtype = getattr(model_cfg, "dtype", None)
exec_inst = DiffusersPipelineExecutor(
model_path=stage_config.model_path,
pipeline_name=pipeline_name,
device=device,
dtype=dtype,
)
self._dit_executors[stage_config.stage_id] = exec_inst
return exec_inst.generate(
prompt=processed_input.get("prompt", ""),
height=processed_input.get("height", getattr(dit, "height", 512)),
width=processed_input.get("width", getattr(dit, "width", 512)),
num_inference_steps=processed_input.get(
"num_inference_steps", getattr(dit, "num_inference_steps", 30)
),
guidance_scale=processed_input.get(
"guidance_scale", getattr(dit, "guidance_scale", 5.0)
),
negative_prompt=processed_input.get("negative_prompt"),
seed=processed_input.get("seed"),
image=processed_input.get("image"),
)
# Use the parent LLM's generate method for AR text generation
prompt = processed_input.get("prompt", "")
max_tokens = processed_input.get("max_tokens", 100)
temperature = processed_input.get("temperature", 0.7)
+64
View File
@@ -0,0 +1,64 @@
"""
Diffusers-backed executor for DiT stages.
Provides a minimal wrapper around HuggingFace diffusers pipelines to support
text-to-image generation as the DiT stage backend.
"""
from __future__ import annotations
from typing import Any, Optional
from vllm_omni.worker.gpu_diffusion_worker import (
DiffusionGPUWorker,
DiffusionRunnerOutput,
)
class DiffusersPipelineExecutor:
"""Thin executor that delegates to a diffusion worker."""
def __init__(
self,
model_path: str,
device: Optional[str] = None,
dtype: Optional[str] = None,
pipeline_name: Optional[str] = None,
) -> None:
try:
import torch # noqa: F401
import diffusers # noqa: F401
except Exception as e: # pragma: no cover
raise ImportError(
"diffusers and torch are required for DiT diffusers backend."
) from e
self.worker = DiffusionGPUWorker(
model_path,
pipeline_name=pipeline_name,
device=device,
dtype=dtype,
)
def generate(
self,
prompt: str,
*,
height: int = 512,
width: int = 512,
num_inference_steps: int = 30,
guidance_scale: float = 5.0,
negative_prompt: Optional[str] = None,
seed: Optional[int] = None,
image: Optional[Any] = None,
) -> DiffusionRunnerOutput:
return self.worker.generate(
prompt=prompt,
height=height,
width=width,
num_inference_steps=num_inference_steps,
guidance_scale=guidance_scale,
negative_prompt=negative_prompt,
seed=seed,
image=image,
)
Whitespace-only changes.
-166
View File
@@ -1,166 +0,0 @@
"""
vLLM plugin system for vLLM-omni integration.
"""
from typing import Dict, Any, Optional
from vllm_omni.entrypoints.omni_llm import OmniLLM, AsyncOmniLLM
from vllm_omni.config import create_ar_stage_config, create_dit_stage_config
class OmniPlugin:
"""vLLM plugin for vLLM-omni integration."""
def __init__(self):
self.name = "omni"
self.version = "0.1.0"
self.description = "Multi-modality models inference and serving"
def register_components(self) -> Dict[str, Any]:
"""Register vLLM-omni components with vLLM."""
return {
"omni_llm": OmniLLM,
"async_omni_llm": AsyncOmniLLM,
"create_ar_stage_config": create_ar_stage_config,
"create_dit_stage_config": create_dit_stage_config,
}
def get_config_schema(self) -> Dict[str, Any]:
"""Get configuration schema for the plugin."""
return {
"type": "object",
"properties": {
"stages": {
"type": "array",
"items": {
"type": "object",
"properties": {
"stage_id": {"type": "integer"},
"engine_type": {"type": "string", "enum": ["AR", "DiT"]},
"model_path": {"type": "string"},
"input_modalities": {
"type": "array",
"items": {"type": "string"}
},
"output_modalities": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["stage_id", "engine_type", "model_path", "input_modalities", "output_modalities"]
}
}
},
"required": ["stages"]
}
def validate_config(self, config: Dict[str, Any]) -> bool:
"""Validate plugin configuration."""
if "stages" not in config:
return False
stages = config["stages"]
if not isinstance(stages, list):
return False
for stage in stages:
required_fields = ["stage_id", "engine_type", "model_path", "input_modalities", "output_modalities"]
for field in required_fields:
if field not in stage:
return False
if stage["engine_type"] not in ["AR", "DiT"]:
return False
return True
def create_omni_llm(self, config: Dict[str, Any]) -> OmniLLM:
"""Create an OmniLLM instance from configuration."""
from vllm_omni.config import OmniStageConfig, DiTConfig
stage_configs = []
for stage_config in config["stages"]:
if stage_config["engine_type"] == "AR":
stage_config_obj = create_ar_stage_config(
stage_id=stage_config["stage_id"],
model_path=stage_config["model_path"],
input_modalities=stage_config["input_modalities"],
output_modalities=stage_config["output_modalities"]
)
elif stage_config["engine_type"] == "DiT":
dit_config = DiTConfig(
model_type="dit",
scheduler_type="ddpm",
num_inference_steps=50,
guidance_scale=7.5
)
stage_config_obj = create_dit_stage_config(
stage_id=stage_config["stage_id"],
model_path=stage_config["model_path"],
input_modalities=stage_config["input_modalities"],
output_modalities=stage_config["output_modalities"],
dit_config=dit_config
)
else:
raise ValueError(f"Unknown engine type: {stage_config['engine_type']}")
stage_configs.append(stage_config_obj)
return OmniLLM(stage_configs)
def create_async_omni_llm(self, config: Dict[str, Any]) -> AsyncOmniLLM:
"""Create an AsyncOmniLLM instance from configuration."""
from vllm_omni.config import OmniStageConfig, DiTConfig
stage_configs = []
for stage_config in config["stages"]:
if stage_config["engine_type"] == "AR":
stage_config_obj = create_ar_stage_config(
stage_id=stage_config["stage_id"],
model_path=stage_config["model_path"],
input_modalities=stage_config["input_modalities"],
output_modalities=stage_config["output_modalities"]
)
elif stage_config["engine_type"] == "DiT":
dit_config = DiTConfig(
model_type="dit",
scheduler_type="ddpm",
num_inference_steps=50,
guidance_scale=7.5
)
stage_config_obj = create_dit_stage_config(
stage_id=stage_config["stage_id"],
model_path=stage_config["model_path"],
input_modalities=stage_config["input_modalities"],
output_modalities=stage_config["output_modalities"],
dit_config=dit_config
)
else:
raise ValueError(f"Unknown engine type: {stage_config['engine_type']}")
stage_configs.append(stage_config_obj)
return AsyncOmniLLM(stage_configs)
def get_help_text(self) -> str:
"""Get help text for the plugin."""
return """
vLLM-omni Plugin
This plugin enables multi-modality models inference and serving with non-autoregressive structures.
Usage:
vllm serve model --omni [options]
Options:
--ar-stage MODEL_PATH AR stage model path
--dit-stage MODEL_PATH DiT stage model path
--dit-steps N Number of DiT inference steps (default: 50)
--dit-guidance-scale F DiT guidance scale (default: 7.5)
--use-diffusers Use diffusers pipeline for DiT stage
Examples:
vllm serve Qwen/Qwen2.5-Omni-7B --omni
vllm serve model --omni --ar-stage text-model --dit-stage image-model
"""
@@ -0,0 +1,133 @@
"""Model runner that wraps a diffusers text-to-image pipeline."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Optional
@dataclass
class DiffusionRunnerOutput:
"""Structured output returned by the diffusion runner."""
prompt: str
images: list
request_id: str = "diffusion_request"
finished: bool = True
output_type: str = "image"
class DiffusionModelRunner:
"""Lightweight runner that loads and executes diffusers pipelines."""
def __init__(
self,
model_path: str,
*,
pipeline_name: Optional[str] = None,
device: Optional[str] = None,
dtype: Optional[str] = None,
) -> None:
self.model_path = model_path
self.pipeline_name = pipeline_name or "auto"
self.device, self.torch_dtype = self._resolve_device_and_dtype(device, dtype)
self._pipeline = self._load_pipeline()
def _resolve_device_and_dtype(
self,
device: Optional[str],
dtype: Optional[str],
):
import torch
resolved_device = device
if resolved_device is None:
if hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
resolved_device = "mps"
elif torch.cuda.is_available():
resolved_device = "cuda"
else:
resolved_device = "cpu"
if dtype is not None:
resolved_dtype = getattr(torch, dtype, None)
else:
if resolved_device in {"cuda", "mps"}:
resolved_dtype = torch.float16
else:
resolved_dtype = torch.float32
return resolved_device, resolved_dtype
def _load_pipeline(self):
import diffusers
if self.pipeline_name != "auto":
PipelineCls = getattr(diffusers, self.pipeline_name)
else:
PipelineCls = diffusers.AutoPipelineForText2Image
pipeline = PipelineCls.from_pretrained(
self.model_path,
torch_dtype=self.torch_dtype,
)
try:
pipeline = pipeline.to(self.device)
except Exception:
pipeline = pipeline.to("cpu")
self._apply_optimisations(pipeline)
return pipeline
@staticmethod
def _apply_optimisations(pipeline) -> None:
# Enable attention slicing / VAE tiling where available to reduce memory.
try:
if hasattr(pipeline, "enable_attention_slicing"):
pipeline.enable_attention_slicing()
except Exception:
pass
try:
vae = getattr(pipeline, "vae", None)
if vae and hasattr(vae, "enable_tiling"):
vae.enable_tiling()
except Exception:
pass
def generate(
self,
prompt: str,
*,
height: int = 512,
width: int = 512,
num_inference_steps: int = 30,
guidance_scale: float = 5.0,
negative_prompt: Optional[str] = None,
seed: Optional[int] = None,
image: Optional[Any] = None,
) -> DiffusionRunnerOutput:
import torch
generator = None
if seed is not None:
try:
generator = torch.Generator(device=self.device).manual_seed(int(seed))
except Exception:
# Some backends (e.g., CPU without full support) may not accept the device.
generator = torch.Generator().manual_seed(int(seed))
output = self._pipeline(
prompt=prompt,
negative_prompt=negative_prompt,
num_inference_steps=int(num_inference_steps),
guidance_scale=float(guidance_scale),
height=int(height),
width=int(width),
generator=generator,
image=image,
)
return DiffusionRunnerOutput(
prompt=prompt,
images=getattr(output, "images", []) or [],
)
+56
View File
@@ -0,0 +1,56 @@
"""Worker abstraction for executing diffusion model runners."""
from __future__ import annotations
from typing import Any, Optional
from vllm.v1.worker.worker_base import WorkerBase
from .gpu_diffusion_model_runner import DiffusionModelRunner, DiffusionRunnerOutput
class DiffusionGPUWorker(WorkerBase):
"""Minimal worker wrapping DiffusionModelRunner.
The worker interface mirrors the shape expected by vLLM executors: it owns
a model runner instance and exposes a `generate` method that can be called
by an executor implementation.
"""
def __init__(
self,
model_path: str,
*,
pipeline_name: Optional[str] = None,
device: Optional[str] = None,
dtype: Optional[str] = None,
) -> None:
self.model_runner = DiffusionModelRunner(
model_path,
pipeline_name=pipeline_name,
device=device,
dtype=dtype,
)
def generate(
self,
prompt: str,
*,
height: int = 512,
width: int = 512,
num_inference_steps: int = 30,
guidance_scale: float = 5.0,
negative_prompt: Optional[str] = None,
seed: Optional[int] = None,
image: Optional[Any] = None,
) -> DiffusionRunnerOutput:
return self.model_runner.generate(
prompt=prompt,
height=height,
width=width,
num_inference_steps=num_inference_steps,
guidance_scale=guidance_scale,
negative_prompt=negative_prompt,
seed=seed,
image=image,
)