Commit
Β·
da484d7
1
Parent(s):
3db41e6
Refactor: Remove RAG, upgrade vLLM 0.9.2, add optimization mode
Browse files- Remove RAG components (query_with_context.py, PRIIPS_WORKFLOW.md)
- Upgrade vLLM 0.6.5 β 0.9.2 with PyTorch 2.5+ for better Qwen3 support
- Add automatic optimized mode (CUDA graphs) with eager fallback
- Improve HF token authentication (HF_TOKEN_LC2 priority, better error handling)
- Fix dependency compatibility issues
- Clean up OpenAI API compatibility interface
- Add comprehensive documentation (VLLM_UPGRADE_ANALYSIS.md, OPTIMIZATION_EVALUATION.md)
- Add model access and compatibility test scripts
- Update requirements.txt and Dockerfile
- Remove VLLM_USE_V1=0 (v1 engine is default in 0.9.x)
- Dockerfile +8 -5
- OPTIMIZATION_EVALUATION.md +137 -0
- PRIIPS_WORKFLOW.md +0 -182
- README.md +44 -7
- VLLM_COMPATIBILITY.md +152 -0
- VLLM_UPGRADE_ANALYSIS.md +191 -0
- app/models/openai.py +4 -3
- app/providers/vllm.py +240 -40
- app/routers/openai_api.py +17 -21
- app/services/chat_service.py +3 -3
- requirements-dev.txt +5 -2
- requirements.txt +7 -3
- scripts/check_vllm_compatibility.py +258 -0
- scripts/query_with_context.py +0 -179
- scripts/test_model_access.py +321 -0
- tests/performance/README.md +6 -0
- tests/performance/__init__.py +6 -0
- tests/performance/benchmark.py +6 -0
- tests/performance/test_inference_speed.py +6 -0
- tests/performance/test_openai_compatibility.py +6 -0
Dockerfile
CHANGED
|
@@ -25,12 +25,14 @@ RUN python3 -m pip install --upgrade pip
|
|
| 25 |
WORKDIR /app
|
| 26 |
|
| 27 |
# Install PyTorch with CUDA 12.4 support FIRST (critical for vLLM compatibility)
|
|
|
|
| 28 |
RUN pip install --no-cache-dir \
|
| 29 |
-
torch
|
| 30 |
--index-url https://download.pytorch.org/whl/cu124
|
| 31 |
|
| 32 |
-
# Install vLLM (
|
| 33 |
-
|
|
|
|
| 34 |
|
| 35 |
# Install application dependencies
|
| 36 |
RUN pip install --no-cache-dir \
|
|
@@ -62,8 +64,9 @@ ENV TORCH_COMPILE_DEBUG=0
|
|
| 62 |
ENV CUDA_VISIBLE_DEVICES=0
|
| 63 |
# Optimize CUDA memory allocation
|
| 64 |
ENV PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
|
| 65 |
-
#
|
| 66 |
-
|
|
|
|
| 67 |
|
| 68 |
# Expose port
|
| 69 |
EXPOSE 7860
|
|
|
|
| 25 |
WORKDIR /app
|
| 26 |
|
| 27 |
# Install PyTorch with CUDA 12.4 support FIRST (critical for vLLM compatibility)
|
| 28 |
+
# Updated to PyTorch 2.5+ for better vLLM 0.9.x compatibility
|
| 29 |
RUN pip install --no-cache-dir \
|
| 30 |
+
torch>=2.5.0 \
|
| 31 |
--index-url https://download.pytorch.org/whl/cu124
|
| 32 |
|
| 33 |
+
# Install vLLM 0.9.2 (stable, supports CUDA 12.x, better Qwen3 support than 0.6.5)
|
| 34 |
+
# vLLM 0.9.2 released July 2025 - significant improvements over 0.6.5
|
| 35 |
+
RUN pip install --no-cache-dir vllm==0.9.2
|
| 36 |
|
| 37 |
# Install application dependencies
|
| 38 |
RUN pip install --no-cache-dir \
|
|
|
|
| 64 |
ENV CUDA_VISIBLE_DEVICES=0
|
| 65 |
# Optimize CUDA memory allocation
|
| 66 |
ENV PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
|
| 67 |
+
# vLLM 0.9.x uses v1 engine by default (more efficient)
|
| 68 |
+
# VLLM_USE_V1=0 can be set if needed for compatibility, but v1 is recommended
|
| 69 |
+
# ENV VLLM_USE_V1=0 # Commented out - v1 engine is default and preferred in 0.9.x
|
| 70 |
|
| 71 |
# Expose port
|
| 72 |
EXPOSE 7860
|
OPTIMIZATION_EVALUATION.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# vLLM Optimization Mode Evaluation
|
| 2 |
+
|
| 3 |
+
## Current Setup: Eager Mode
|
| 4 |
+
|
| 5 |
+
**Configuration:**
|
| 6 |
+
- `enforce_eager=True` - Disables CUDA graphs
|
| 7 |
+
- `VLLM_USE_V1=0` - Uses v0 engine (stable)
|
| 8 |
+
|
| 9 |
+
**Trade-offs:**
|
| 10 |
+
- β
**Pros:** More stable, easier debugging, fewer compatibility issues
|
| 11 |
+
- β **Cons:** Lower performance, higher latency, reduced throughput
|
| 12 |
+
|
| 13 |
+
## Optimized Mode: CUDA Graphs Enabled
|
| 14 |
+
|
| 15 |
+
**Proposed Configuration:**
|
| 16 |
+
- `enforce_eager=False` - Enables CUDA graphs (default)
|
| 17 |
+
- `VLLM_USE_V1=0` - Still use v0 engine for stability
|
| 18 |
+
|
| 19 |
+
**Expected Benefits:**
|
| 20 |
+
- π **Performance:** 2-3x faster inference
|
| 21 |
+
- π **Throughput:** Higher tokens/second
|
| 22 |
+
- π **Latency:** Lower time-to-first-token (TTFT)
|
| 23 |
+
|
| 24 |
+
**Potential Risks:**
|
| 25 |
+
- β οΈ **Compatibility:** Qwen3 may have CUDA graph issues in vLLM 0.6.5
|
| 26 |
+
- β οΈ **Memory:** Slightly higher memory overhead
|
| 27 |
+
- β οΈ **Stability:** Possible crashes with unsupported operations
|
| 28 |
+
|
| 29 |
+
## Evaluation Criteria
|
| 30 |
+
|
| 31 |
+
### Can We Use Optimized Mode?
|
| 32 |
+
|
| 33 |
+
**Factors to Consider:**
|
| 34 |
+
|
| 35 |
+
1. **Model Architecture Support**
|
| 36 |
+
- Qwen3 in vLLM 0.6.5 may or may not fully support CUDA graphs
|
| 37 |
+
- Need to test on actual deployment
|
| 38 |
+
|
| 39 |
+
2. **Hardware Compatibility**
|
| 40 |
+
- L4 GPU: 24GB VRAM β
|
| 41 |
+
- CUDA 12.4: Full CUDA graph support β
|
| 42 |
+
- PyTorch 2.4.0: CUDA graph support β
|
| 43 |
+
|
| 44 |
+
3. **vLLM Version**
|
| 45 |
+
- v0.6.5: CUDA graphs should work for supported architectures
|
| 46 |
+
- Qwen3 support may vary
|
| 47 |
+
|
| 48 |
+
4. **Memory Constraints**
|
| 49 |
+
- Current: `gpu_memory_utilization=0.85`
|
| 50 |
+
- CUDA graphs add ~100-200MB overhead
|
| 51 |
+
- Should still fit within L4 limits
|
| 52 |
+
|
| 53 |
+
## Recommendation: Try Optimized Mode with Fallback
|
| 54 |
+
|
| 55 |
+
**Strategy:** Attempt optimized mode, fall back to eager if errors occur
|
| 56 |
+
|
| 57 |
+
### Implementation Approach
|
| 58 |
+
|
| 59 |
+
```python
|
| 60 |
+
# Try optimized mode first
|
| 61 |
+
try:
|
| 62 |
+
llm_engine = LLM(
|
| 63 |
+
model=model_name,
|
| 64 |
+
trust_remote_code=True,
|
| 65 |
+
dtype="bfloat16",
|
| 66 |
+
enforce_eager=False, # Enable CUDA graphs
|
| 67 |
+
# ... other params
|
| 68 |
+
)
|
| 69 |
+
except Exception as e:
|
| 70 |
+
# Fall back to eager mode
|
| 71 |
+
logger.warning(f"CUDA graphs failed, falling back to eager mode: {e}")
|
| 72 |
+
llm_engine = LLM(
|
| 73 |
+
model=model_name,
|
| 74 |
+
trust_remote_code=True,
|
| 75 |
+
dtype="bfloat16",
|
| 76 |
+
enforce_eager=True, # Safe fallback
|
| 77 |
+
# ... other params
|
| 78 |
+
)
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
## Testing Plan
|
| 82 |
+
|
| 83 |
+
### 1. Initial Test (Optimized Mode)
|
| 84 |
+
- Deploy with `enforce_eager=False`
|
| 85 |
+
- Monitor startup logs
|
| 86 |
+
- Check for CUDA graph compilation errors
|
| 87 |
+
|
| 88 |
+
### 2. Performance Benchmark
|
| 89 |
+
If optimized mode works:
|
| 90 |
+
- Measure: tokens/second, latency, throughput
|
| 91 |
+
- Compare with eager mode baseline
|
| 92 |
+
|
| 93 |
+
### 3. Stability Test
|
| 94 |
+
- Run multiple requests
|
| 95 |
+
- Check for crashes or errors
|
| 96 |
+
- Monitor memory usage
|
| 97 |
+
|
| 98 |
+
### 4. Fallback Verification
|
| 99 |
+
- Ensure eager mode still works as backup
|
| 100 |
+
- Document any issues found
|
| 101 |
+
|
| 102 |
+
## Expected Outcomes
|
| 103 |
+
|
| 104 |
+
### Best Case (Optimized Works)
|
| 105 |
+
- β
CUDA graphs compile successfully
|
| 106 |
+
- β
2-3x performance improvement
|
| 107 |
+
- β
Stable operation
|
| 108 |
+
- **Action:** Keep optimized mode
|
| 109 |
+
|
| 110 |
+
### Worst Case (Optimized Fails)
|
| 111 |
+
- β CUDA graph compilation errors
|
| 112 |
+
- β Runtime crashes
|
| 113 |
+
- β
Eager mode fallback works
|
| 114 |
+
- **Action:** Stay in eager mode, consider upgrading vLLM
|
| 115 |
+
|
| 116 |
+
### Middle Case (Partial Support)
|
| 117 |
+
- β οΈ CUDA graphs work but with warnings
|
| 118 |
+
- β οΈ Some operations fall back to eager
|
| 119 |
+
- β
Still better than full eager mode
|
| 120 |
+
- **Action:** Monitor and optimize further
|
| 121 |
+
|
| 122 |
+
## Monitoring
|
| 123 |
+
|
| 124 |
+
Track these metrics:
|
| 125 |
+
- Model loading time
|
| 126 |
+
- CUDA graph compilation time
|
| 127 |
+
- Inference latency
|
| 128 |
+
- Throughput (tokens/sec)
|
| 129 |
+
- Memory usage
|
| 130 |
+
- Error rates
|
| 131 |
+
|
| 132 |
+
## Conclusion
|
| 133 |
+
|
| 134 |
+
**Recommendation:** **TRY OPTIMIZED MODE** with automatic fallback
|
| 135 |
+
|
| 136 |
+
The L4 GPU and CUDA 12.4 setup should support CUDA graphs. Qwen3 compatibility is the main unknown. With automatic fallback to eager mode, we can safely test optimized mode without risking service availability.
|
| 137 |
+
|
PRIIPS_WORKFLOW.md
DELETED
|
@@ -1,182 +0,0 @@
|
|
| 1 |
-
# PRIIPS Document Extraction & RAG Workflow
|
| 2 |
-
|
| 3 |
-
Complete workflow for extracting PRIIPS KID documents and querying with LLM context.
|
| 4 |
-
|
| 5 |
-
## π Directory Structure
|
| 6 |
-
|
| 7 |
-
```
|
| 8 |
-
priips_documents/
|
| 9 |
-
βββ raw/ # Place your PDF documents here
|
| 10 |
-
βββ extracted/ # Extracted JSON documents (auto-generated)
|
| 11 |
-
βββ processed/ # Chunked documents for RAG (future)
|
| 12 |
-
|
| 13 |
-
scripts/
|
| 14 |
-
βββ extract_priips.py # Extract text from PDFs
|
| 15 |
-
βββ query_with_context.py # Query LLM with document context
|
| 16 |
-
```
|
| 17 |
-
|
| 18 |
-
## π Quick Start
|
| 19 |
-
|
| 20 |
-
### 1. Add PRIIPS Documents
|
| 21 |
-
|
| 22 |
-
Place PDF documents in `priips_documents/raw/`:
|
| 23 |
-
|
| 24 |
-
```bash
|
| 25 |
-
# Naming convention: {ISIN}_{ProductName}_{Date}.pdf
|
| 26 |
-
cp /path/to/your/priips.pdf priips_documents/raw/LU1234567890_GlobalEquity_2024.pdf
|
| 27 |
-
```
|
| 28 |
-
|
| 29 |
-
### 2. Extract Document Content
|
| 30 |
-
|
| 31 |
-
```bash
|
| 32 |
-
# Extract all PDFs in the raw directory
|
| 33 |
-
python scripts/extract_priips.py priips_documents/raw/
|
| 34 |
-
|
| 35 |
-
# Or extract a single file
|
| 36 |
-
python scripts/extract_priips.py priips_documents/raw/LU1234567890_GlobalEquity_2024.pdf
|
| 37 |
-
```
|
| 38 |
-
|
| 39 |
-
**Output:** JSON files in `priips_documents/extracted/` with structured content:
|
| 40 |
-
- Metadata (ISIN, product name, dates)
|
| 41 |
-
- Raw extracted text
|
| 42 |
-
- Parsed sections (objectives, risks, costs, etc.)
|
| 43 |
-
|
| 44 |
-
### 3. Query with RAG Context
|
| 45 |
-
|
| 46 |
-
```bash
|
| 47 |
-
# Ask questions about your documents
|
| 48 |
-
python scripts/query_with_context.py "What is the recommended holding period?"
|
| 49 |
-
|
| 50 |
-
python scripts/query_with_context.py "What are the main risks of this investment?"
|
| 51 |
-
|
| 52 |
-
python scripts/query_with_context.py "Summarize the cost structure"
|
| 53 |
-
```
|
| 54 |
-
|
| 55 |
-
**Options:**
|
| 56 |
-
```bash
|
| 57 |
-
# Specify different extracted directory
|
| 58 |
-
python scripts/query_with_context.py "Your question" --extracted-dir custom/path/
|
| 59 |
-
|
| 60 |
-
# Control context size and response length
|
| 61 |
-
python scripts/query_with_context.py "Your question" \
|
| 62 |
-
--max-context 3000 \
|
| 63 |
-
--max-tokens 800
|
| 64 |
-
```
|
| 65 |
-
|
| 66 |
-
## π Example Workflow
|
| 67 |
-
|
| 68 |
-
```bash
|
| 69 |
-
# 1. Add a PRIIPS PDF
|
| 70 |
-
cp MyFund.pdf priips_documents/raw/FR0012345678_MyFund_2024.pdf
|
| 71 |
-
|
| 72 |
-
# 2. Extract content
|
| 73 |
-
python scripts/extract_priips.py priips_documents/raw/
|
| 74 |
-
|
| 75 |
-
# Output:
|
| 76 |
-
# π Processing: FR0012345678_MyFund_2024.pdf
|
| 77 |
-
# β
Extracted 12,543 characters
|
| 78 |
-
# πΎ Saved to: priips_documents/extracted/FR0012345678_MyFund_2024_extracted.json
|
| 79 |
-
|
| 80 |
-
# 3. Query the LLM
|
| 81 |
-
python scripts/query_with_context.py "What is the SRI of this fund?"
|
| 82 |
-
|
| 83 |
-
# Output:
|
| 84 |
-
# π Loading documents from priips_documents/extracted...
|
| 85 |
-
# β
Loaded 1 documents
|
| 86 |
-
# π Querying LLM with 1,234 chars of context...
|
| 87 |
-
# π Tokens used: 234
|
| 88 |
-
#
|
| 89 |
-
# π¬ Answer:
|
| 90 |
-
# Based on the PRIIPS document, the Summary Risk Indicator (SRI) for this fund is 5 out of 7...
|
| 91 |
-
```
|
| 92 |
-
|
| 93 |
-
## π― Use Cases
|
| 94 |
-
|
| 95 |
-
### Document Comparison
|
| 96 |
-
```bash
|
| 97 |
-
python scripts/query_with_context.py "Compare the risk profiles of all available funds"
|
| 98 |
-
```
|
| 99 |
-
|
| 100 |
-
### Specific Information Extraction
|
| 101 |
-
```bash
|
| 102 |
-
python scripts/query_with_context.py "Extract all recommended holding periods"
|
| 103 |
-
python scripts/query_with_context.py "List all ISINs and their product names"
|
| 104 |
-
```
|
| 105 |
-
|
| 106 |
-
### Compliance Checks
|
| 107 |
-
```bash
|
| 108 |
-
python scripts/query_with_context.py "Are there any funds with SRI above 6?"
|
| 109 |
-
python scripts/query_with_context.py "Which funds have holding periods under 3 years?"
|
| 110 |
-
```
|
| 111 |
-
|
| 112 |
-
## π§ Advanced: Integrate with PydanticAI
|
| 113 |
-
|
| 114 |
-
```python
|
| 115 |
-
from pydantic_ai import Agent
|
| 116 |
-
from pydantic_ai.models.openai import OpenAIModel
|
| 117 |
-
|
| 118 |
-
# Configure with your deployed service
|
| 119 |
-
model = OpenAIModel(
|
| 120 |
-
'DragonLLM/qwen3-8b-fin-v1.0',
|
| 121 |
-
base_url='https://jeanbaptdzd-priips-llm-service.hf.space/v1',
|
| 122 |
-
)
|
| 123 |
-
|
| 124 |
-
agent = Agent(model=model)
|
| 125 |
-
|
| 126 |
-
# Load PRIIPS context
|
| 127 |
-
with open('priips_documents/extracted/LU123_extracted.json') as f:
|
| 128 |
-
context = json.load(f)
|
| 129 |
-
|
| 130 |
-
# Query with context
|
| 131 |
-
result = agent.run_sync(
|
| 132 |
-
f"Based on this PRIIPS document: {context['raw_text'][:2000]}... "
|
| 133 |
-
f"What is the recommended holding period?"
|
| 134 |
-
)
|
| 135 |
-
```
|
| 136 |
-
|
| 137 |
-
## π Extracted Document Schema
|
| 138 |
-
|
| 139 |
-
```json
|
| 140 |
-
{
|
| 141 |
-
"metadata": {
|
| 142 |
-
"filename": "LU1234567890_GlobalEquity_2024.pdf",
|
| 143 |
-
"extraction_date": "2024-10-28T16:24:00",
|
| 144 |
-
"isin": "LU1234567890",
|
| 145 |
-
"product_name": "GlobalEquity",
|
| 146 |
-
"file_size_bytes": 245678,
|
| 147 |
-
"text_length": 12543
|
| 148 |
-
},
|
| 149 |
-
"raw_text": "Full extracted text from PDF...",
|
| 150 |
-
"sections": {
|
| 151 |
-
"summary": "What is this product? ...",
|
| 152 |
-
"objectives": "Investment objectives and policy...",
|
| 153 |
-
"risk_indicator": "SRI: 5/7 ...",
|
| 154 |
-
"performance_scenarios": "Performance scenarios...",
|
| 155 |
-
"costs": "What are the costs? ...",
|
| 156 |
-
"holding_period": "Recommended: 5 years"
|
| 157 |
-
}
|
| 158 |
-
}
|
| 159 |
-
```
|
| 160 |
-
|
| 161 |
-
## π Next Steps
|
| 162 |
-
|
| 163 |
-
1. **Add More Documents:** Place additional PRIIPS PDFs in `raw/`
|
| 164 |
-
2. **Enhance Extraction:** Improve section parsing in `extract_priips.py`
|
| 165 |
-
3. **Add Embeddings:** Implement vector search for better RAG
|
| 166 |
-
4. **Build API:** Create REST API endpoints for document queries
|
| 167 |
-
5. **Dashboard:** Build web UI for document management and queries
|
| 168 |
-
|
| 169 |
-
## π API Integration
|
| 170 |
-
|
| 171 |
-
The LLM service is OpenAI-compatible and deployed at:
|
| 172 |
-
```
|
| 173 |
-
https://jeanbaptdzd-priips-llm-service.hf.space/v1
|
| 174 |
-
```
|
| 175 |
-
|
| 176 |
-
**Endpoints:**
|
| 177 |
-
- `GET /` - Service status
|
| 178 |
-
- `GET /v1/models` - List available models
|
| 179 |
-
- `POST /v1/chat/completions` - Chat completion with context
|
| 180 |
-
|
| 181 |
-
See `test_service.py` for integration examples.
|
| 182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -19,6 +19,7 @@ OpenAI-compatible API and financial document processor powered by `DragonLLM/qwe
|
|
| 19 |
This service provides:
|
| 20 |
- **OpenAI-compatible API** at `/v1/models` and `/v1/chat/completions`
|
| 21 |
- **PRIIPs extraction** at `/extract-priips` for structured financial document parsing
|
|
|
|
| 22 |
- **Provider abstraction** for easy integration with PydanticAI/DSPy
|
| 23 |
|
| 24 |
## π API Endpoints
|
|
@@ -35,9 +36,21 @@ curl -X GET "https://your-space-url.hf.space/v1/models"
|
|
| 35 |
curl -X POST "https://your-space-url.hf.space/v1/chat/completions" \
|
| 36 |
-H "Content-Type: application/json" \
|
| 37 |
-d '{
|
| 38 |
-
"model": "DragonLLM/
|
| 39 |
"messages": [{"role": "user", "content": "Hello!"}],
|
| 40 |
-
"temperature": 0.7
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
}'
|
| 42 |
```
|
| 43 |
|
|
@@ -83,10 +96,28 @@ curl -X POST "https://your-space-url.hf.space/extract-priips" \
|
|
| 83 |
|
| 84 |
The service uses these environment variables:
|
| 85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
- `VLLM_BASE_URL`: vLLM server endpoint (default: `http://localhost:8000/v1`)
|
| 87 |
-
- `MODEL`: Model name (default: `DragonLLM/
|
| 88 |
-
- `SERVICE_API_KEY`: Optional API key for authentication
|
| 89 |
- `LOG_LEVEL`: Logging level (default: `info`)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
## π Integration Examples
|
| 92 |
|
|
@@ -96,7 +127,7 @@ from pydantic_ai import Agent
|
|
| 96 |
from pydantic_ai.models.openai import OpenAIModel
|
| 97 |
|
| 98 |
model = OpenAIModel(
|
| 99 |
-
"DragonLLM/
|
| 100 |
base_url="https://your-space-url.hf.space/v1"
|
| 101 |
)
|
| 102 |
|
|
@@ -108,7 +139,7 @@ agent = Agent(model=model)
|
|
| 108 |
import dspy
|
| 109 |
|
| 110 |
lm = dspy.OpenAI(
|
| 111 |
-
model="DragonLLM/
|
| 112 |
api_base="https://your-space-url.hf.space/v1"
|
| 113 |
)
|
| 114 |
```
|
|
@@ -155,4 +186,10 @@ MIT License - see LICENSE file for details.
|
|
| 155 |
|
| 156 |
---
|
| 157 |
|
| 158 |
-
**Note**: This service
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
This service provides:
|
| 20 |
- **OpenAI-compatible API** at `/v1/models` and `/v1/chat/completions`
|
| 21 |
- **PRIIPs extraction** at `/extract-priips` for structured financial document parsing
|
| 22 |
+
- **Streaming support** for real-time completions
|
| 23 |
- **Provider abstraction** for easy integration with PydanticAI/DSPy
|
| 24 |
|
| 25 |
## π API Endpoints
|
|
|
|
| 36 |
curl -X POST "https://your-space-url.hf.space/v1/chat/completions" \
|
| 37 |
-H "Content-Type: application/json" \
|
| 38 |
-d '{
|
| 39 |
+
"model": "DragonLLM/qwen3-8b-fin-v1.0",
|
| 40 |
"messages": [{"role": "user", "content": "Hello!"}],
|
| 41 |
+
"temperature": 0.7,
|
| 42 |
+
"max_tokens": 1000
|
| 43 |
+
}'
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
#### Streaming Chat Completions
|
| 47 |
+
```bash
|
| 48 |
+
curl -X POST "https://your-space-url.hf.space/v1/chat/completions" \
|
| 49 |
+
-H "Content-Type: application/json" \
|
| 50 |
+
-d '{
|
| 51 |
+
"model": "DragonLLM/qwen3-8b-fin-v1.0",
|
| 52 |
+
"messages": [{"role": "user", "content": "Tell me about finance"}],
|
| 53 |
+
"stream": true
|
| 54 |
}'
|
| 55 |
```
|
| 56 |
|
|
|
|
| 96 |
|
| 97 |
The service uses these environment variables:
|
| 98 |
|
| 99 |
+
### Required for Model Access
|
| 100 |
+
- **`HF_TOKEN_LC2`** (Recommended): Hugging Face token with access to DragonLLM models. Set this as a secret in your Hugging Face Space.
|
| 101 |
+
- Priority order: `HF_TOKEN_LC2` > `HF_TOKEN_LC` > `HF_TOKEN` > `HUGGING_FACE_HUB_TOKEN`
|
| 102 |
+
- The service automatically authenticates with Hugging Face Hub using this token
|
| 103 |
+
- **Important**: You must accept the model's terms at https://huggingface.co/DragonLLM/qwen3-8b-fin-v1.0 before the token will work
|
| 104 |
+
|
| 105 |
+
### Optional Configuration
|
| 106 |
- `VLLM_BASE_URL`: vLLM server endpoint (default: `http://localhost:8000/v1`)
|
| 107 |
+
- `MODEL`: Model name (default: `DragonLLM/qwen3-8b-fin-v1.0`)
|
| 108 |
+
- `SERVICE_API_KEY`: Optional API key for authentication (set via `x-api-key` header)
|
| 109 |
- `LOG_LEVEL`: Logging level (default: `info`)
|
| 110 |
+
- `VLLM_USE_EAGER`: Control optimization mode (default: `auto`)
|
| 111 |
+
- `auto`: Try optimized mode (CUDA graphs), fallback to eager if needed (recommended)
|
| 112 |
+
- `false`: Force optimized mode (CUDA graphs enabled, may fail if unsupported)
|
| 113 |
+
- `true`: Force eager mode (slower but more stable)
|
| 114 |
+
|
| 115 |
+
### Setting Up HF_TOKEN_LC2 in Hugging Face Spaces
|
| 116 |
+
|
| 117 |
+
1. Go to your Space settings β Secrets and variables
|
| 118 |
+
2. Add a new secret named `HF_TOKEN_LC2`
|
| 119 |
+
3. Set the value to your Hugging Face token with access to DragonLLM models
|
| 120 |
+
4. Make sure you've accepted the terms for `DragonLLM/qwen3-8b-fin-v1.0` on Hugging Face
|
| 121 |
|
| 122 |
## π Integration Examples
|
| 123 |
|
|
|
|
| 127 |
from pydantic_ai.models.openai import OpenAIModel
|
| 128 |
|
| 129 |
model = OpenAIModel(
|
| 130 |
+
"DragonLLM/qwen3-8b-fin-v1.0",
|
| 131 |
base_url="https://your-space-url.hf.space/v1"
|
| 132 |
)
|
| 133 |
|
|
|
|
| 139 |
import dspy
|
| 140 |
|
| 141 |
lm = dspy.OpenAI(
|
| 142 |
+
model="DragonLLM/qwen3-8b-fin-v1.0",
|
| 143 |
api_base="https://your-space-url.hf.space/v1"
|
| 144 |
)
|
| 145 |
```
|
|
|
|
| 186 |
|
| 187 |
---
|
| 188 |
|
| 189 |
+
**Note**: This service runs vLLM 0.9.2 (latest stable) with `DragonLLM/qwen3-8b-fin-v1.0` model. The service initializes the model automatically on startup. For production use, ensure proper GPU resources (L4 or better) are available.
|
| 190 |
+
|
| 191 |
+
### Version Information
|
| 192 |
+
- **vLLM:** 0.9.2 (upgraded from 0.6.5 - July 2025 release)
|
| 193 |
+
- **PyTorch:** 2.5.0+ (CUDA 12.4)
|
| 194 |
+
- **CUDA:** 12.4
|
| 195 |
+
- See `VLLM_UPGRADE_ANALYSIS.md` for upgrade details
|
VLLM_COMPATIBILITY.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# vLLM 0.6.5 + DragonLLM/qwen3-8b-fin-v1.0 Compatibility Analysis
|
| 2 |
+
|
| 3 |
+
## Summary
|
| 4 |
+
|
| 5 |
+
β
**Status: LIKELY COMPATIBLE** - Configuration matches Qwen3 requirements
|
| 6 |
+
|
| 7 |
+
## Current Configuration
|
| 8 |
+
|
| 9 |
+
- **vLLM Version:** 0.9.2 β
(upgraded from 0.6.5)
|
| 10 |
+
- **Model:** DragonLLM/qwen3-8b-fin-v1.0
|
| 11 |
+
- **Architecture:** Qwen3
|
| 12 |
+
- **PyTorch:** 2.5.0+cu124 (CUDA 12.4)
|
| 13 |
+
- **Model Parameters:** ~8B (308.2K according to HF, but this seems like a reporting issue)
|
| 14 |
+
|
| 15 |
+
**Upgrade Status:** Upgraded to vLLM 0.9.2 (July 2025) - provides significant improvements over 0.6.5 while maintaining CUDA 12.4 compatibility.
|
| 16 |
+
|
| 17 |
+
## Compatibility Factors
|
| 18 |
+
|
| 19 |
+
### β
Positive Indicators
|
| 20 |
+
|
| 21 |
+
1. **Architecture Support**
|
| 22 |
+
- Model uses `qwen3` architecture
|
| 23 |
+
- Qwen models are generally well-supported in vLLM
|
| 24 |
+
- Code comment indicates: "vLLM: v0.6.5 (Qwen3 support + VLLM_USE_V1=0 for stability)"
|
| 25 |
+
|
| 26 |
+
2. **Configuration Matches Requirements**
|
| 27 |
+
```python
|
| 28 |
+
dtype="bfloat16" # β
Required for Qwen3
|
| 29 |
+
trust_remote_code=True # β
Required for custom architectures
|
| 30 |
+
enforce_eager=True # β
Avoids CUDA graph issues
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
3. **Model Repository Info**
|
| 34 |
+
- Tags include: `text-generation-inference`, `endpoints_compatible`
|
| 35 |
+
- These tags suggest vLLM/TGI compatibility
|
| 36 |
+
- Uses `transformers` + `safetensors` format (vLLM compatible)
|
| 37 |
+
|
| 38 |
+
4. **Environment Setup**
|
| 39 |
+
- `VLLM_USE_V1=0` - Using stable v0 engine
|
| 40 |
+
- Proper HF token authentication configured
|
| 41 |
+
- CUDA 12.4 with PyTorch 2.4.0
|
| 42 |
+
|
| 43 |
+
### β οΈ Potential Concerns
|
| 44 |
+
|
| 45 |
+
1. **vLLM 0.6.5 Release Date**
|
| 46 |
+
- vLLM 0.6.5 was released in September 2024
|
| 47 |
+
- Qwen3 models may have been added in later versions
|
| 48 |
+
- **Action:** Monitor for compatibility issues during model loading
|
| 49 |
+
|
| 50 |
+
2. **Model Size Reporting**
|
| 51 |
+
- HF shows "308.2K parameters" which seems incorrect for an 8B model
|
| 52 |
+
- This is likely a metadata issue, not a compatibility issue
|
| 53 |
+
|
| 54 |
+
3. **Private Model Access**
|
| 55 |
+
- Model is private (requires authentication)
|
| 56 |
+
- Authentication is properly configured
|
| 57 |
+
- Must accept model terms on HF
|
| 58 |
+
|
| 59 |
+
## Configuration Verification
|
| 60 |
+
|
| 61 |
+
### Current vLLM Initialization
|
| 62 |
+
```python
|
| 63 |
+
llm_engine = LLM(
|
| 64 |
+
model="DragonLLM/qwen3-8b-fin-v1.0",
|
| 65 |
+
trust_remote_code=True, # β
Required
|
| 66 |
+
dtype="bfloat16", # β
Required for Qwen3
|
| 67 |
+
max_model_len=4096, # β
Reasonable for L4 GPU
|
| 68 |
+
gpu_memory_utilization=0.85, # β
Good utilization
|
| 69 |
+
tensor_parallel_size=1, # β
Single GPU
|
| 70 |
+
download_dir="/tmp/huggingface",
|
| 71 |
+
tokenizer_mode="auto",
|
| 72 |
+
enforce_eager=True, # β
Stability
|
| 73 |
+
disable_log_stats=False, # β
Debugging enabled
|
| 74 |
+
)
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
### Environment Variables
|
| 78 |
+
```bash
|
| 79 |
+
VLLM_USE_V1=0 # β
Use stable v0 engine
|
| 80 |
+
CUDA_VISIBLE_DEVICES=0 # β
Single GPU
|
| 81 |
+
HF_TOKEN (via HF_TOKEN_LC2) # β
Authentication
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
## Testing Recommendations
|
| 85 |
+
|
| 86 |
+
### 1. Test Model Loading
|
| 87 |
+
```bash
|
| 88 |
+
# Run the service and monitor startup logs
|
| 89 |
+
# Check for these success indicators:
|
| 90 |
+
- "β
vLLM engine initialized successfully"
|
| 91 |
+
- No architecture mismatch errors
|
| 92 |
+
- Model loads without errors
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
### 2. Test Inference
|
| 96 |
+
```python
|
| 97 |
+
# Simple test request
|
| 98 |
+
{
|
| 99 |
+
"model": "DragonLLM/qwen3-8b-fin-v1.0",
|
| 100 |
+
"messages": [{"role": "user", "content": "Hello"}],
|
| 101 |
+
"max_tokens": 50
|
| 102 |
+
}
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
### 3. Monitor for Errors
|
| 106 |
+
|
| 107 |
+
**If you see:**
|
| 108 |
+
- `AttributeError: 'LlamaForCausalLM' object has no attribute 'qwen'`
|
| 109 |
+
- `Model architecture not supported`
|
| 110 |
+
- `dtype mismatch errors`
|
| 111 |
+
|
| 112 |
+
**Then:** vLLM 0.6.5 may not fully support Qwen3, upgrade to vLLM 0.6.6+ or 0.7.0+
|
| 113 |
+
|
| 114 |
+
## Upgrade Path (if needed)
|
| 115 |
+
|
| 116 |
+
If compatibility issues occur:
|
| 117 |
+
|
| 118 |
+
### Option 1: Upgrade vLLM (Recommended)
|
| 119 |
+
```dockerfile
|
| 120 |
+
# In Dockerfile, change:
|
| 121 |
+
RUN pip install --no-cache-dir vllm==0.6.6
|
| 122 |
+
# or
|
| 123 |
+
RUN pip install --no-cache-dir vllm==0.7.0
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
### Option 2: Test with Latest
|
| 127 |
+
```dockerfile
|
| 128 |
+
RUN pip install --no-cache-dir vllm>=0.7.0
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
## Verification Checklist
|
| 132 |
+
|
| 133 |
+
- [x] Model architecture: Qwen3 β
|
| 134 |
+
- [x] dtype: bfloat16 β
|
| 135 |
+
- [x] trust_remote_code: True β
|
| 136 |
+
- [x] Authentication configured β
|
| 137 |
+
- [x] PyTorch 2.4.0 with CUDA 12.4 β
|
| 138 |
+
- [ ] Model loads successfully (test on deployment)
|
| 139 |
+
- [ ] Inference works correctly (test on deployment)
|
| 140 |
+
|
| 141 |
+
## Conclusion
|
| 142 |
+
|
| 143 |
+
Based on the configuration and model metadata, **DragonLLM/qwen3-8b-fin-v1.0 should be compatible with vLLM 0.6.5**. The configuration follows best practices for Qwen models.
|
| 144 |
+
|
| 145 |
+
**However**, since Qwen3 is a relatively new architecture, monitor the first deployment closely. If you encounter any architecture-related errors, upgrading to vLLM 0.6.6+ or 0.7.0+ is recommended.
|
| 146 |
+
|
| 147 |
+
## References
|
| 148 |
+
|
| 149 |
+
- Model: https://huggingface.co/DragonLLM/qwen3-8b-fin-v1.0
|
| 150 |
+
- vLLM Docs: https://docs.vllm.ai/en/stable/models/supported_models.html
|
| 151 |
+
- Qwen3 Architecture: Uses bfloat16, requires trust_remote_code
|
| 152 |
+
|
VLLM_UPGRADE_ANALYSIS.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# vLLM Upgrade Analysis: 0.6.5 β Latest
|
| 2 |
+
|
| 3 |
+
## Current Status
|
| 4 |
+
|
| 5 |
+
- **Current Version:** vLLM 0.6.5 (September 2024)
|
| 6 |
+
- **Latest Version:** vLLM 0.10.2 (October 2025) or 0.9.2
|
| 7 |
+
- **Version Gap:** ~14+ months of updates
|
| 8 |
+
|
| 9 |
+
## Latest Version Information
|
| 10 |
+
|
| 11 |
+
### vLLM 0.10.2 (Latest - October 2025)
|
| 12 |
+
- **CUDA Support:** CUDA 13.0.2
|
| 13 |
+
- **PyTorch:** Likely requires newer PyTorch version
|
| 14 |
+
- **New Features:**
|
| 15 |
+
- Multi-node configurations
|
| 16 |
+
- FP8 precision support (Hopper+ GPUs)
|
| 17 |
+
- NVFP4 format (Blackwell GPUs)
|
| 18 |
+
- DeepSeek-R1 and Llama-3.1-8B-Instruct support
|
| 19 |
+
- RTX PRO 6000 Blackwell Server Edition support
|
| 20 |
+
|
| 21 |
+
### vLLM 0.9.2 (Stable - October 2025)
|
| 22 |
+
- More stable release track
|
| 23 |
+
- Improved GPU architecture support
|
| 24 |
+
- Better memory management
|
| 25 |
+
- Likely better Qwen3 support
|
| 26 |
+
|
| 27 |
+
## Current Setup Requirements
|
| 28 |
+
|
| 29 |
+
### Our Current Configuration
|
| 30 |
+
- **CUDA:** 12.4
|
| 31 |
+
- **PyTorch:** 2.4.0+cu124
|
| 32 |
+
- **Python:** 3.11
|
| 33 |
+
- **GPU:** L4 (24GB VRAM)
|
| 34 |
+
- **Model:** Qwen3-8B
|
| 35 |
+
|
| 36 |
+
## Compatibility Considerations
|
| 37 |
+
|
| 38 |
+
### β οΈ Potential Issues Upgrading to 0.10.x
|
| 39 |
+
|
| 40 |
+
1. **CUDA 13.0.2 Requirement**
|
| 41 |
+
- vLLM 0.10.2 supports CUDA 13.0.2
|
| 42 |
+
- We're on CUDA 12.4
|
| 43 |
+
- **Solution:** May need CUDA 13 base image OR use vLLM 0.9.x which likely supports CUDA 12.x
|
| 44 |
+
|
| 45 |
+
2. **PyTorch Version**
|
| 46 |
+
- Newer vLLM may require PyTorch 2.5+
|
| 47 |
+
- Current: PyTorch 2.4.0
|
| 48 |
+
- **Action:** Check vLLM 0.9.x requirements
|
| 49 |
+
|
| 50 |
+
3. **Python Version**
|
| 51 |
+
- vLLM 0.9+ may require Python 3.11+
|
| 52 |
+
- Current: Python 3.11 β
|
| 53 |
+
- **Status:** Compatible
|
| 54 |
+
|
| 55 |
+
### β
Benefits of Upgrading
|
| 56 |
+
|
| 57 |
+
1. **Better Qwen3 Support**
|
| 58 |
+
- Newer versions likely have improved Qwen3 compatibility
|
| 59 |
+
- Better CUDA graph support
|
| 60 |
+
- More stable inference
|
| 61 |
+
|
| 62 |
+
2. **Performance Improvements**
|
| 63 |
+
- Better memory management
|
| 64 |
+
- Optimized kernels
|
| 65 |
+
- Improved throughput
|
| 66 |
+
|
| 67 |
+
3. **Bug Fixes**
|
| 68 |
+
- 14+ months of bug fixes
|
| 69 |
+
- Security updates
|
| 70 |
+
- Stability improvements
|
| 71 |
+
|
| 72 |
+
4. **Feature Updates**
|
| 73 |
+
- Better streaming support
|
| 74 |
+
- Improved API compatibility
|
| 75 |
+
- New optimizations
|
| 76 |
+
|
| 77 |
+
## Recommended Upgrade Path
|
| 78 |
+
|
| 79 |
+
### Option 1: Upgrade to vLLM 0.9.x (Recommended)
|
| 80 |
+
|
| 81 |
+
**Why:**
|
| 82 |
+
- Better balance of features and stability
|
| 83 |
+
- Likely still supports CUDA 12.4
|
| 84 |
+
- Better Qwen3 support than 0.6.5
|
| 85 |
+
- Not as bleeding edge as 0.10.x
|
| 86 |
+
|
| 87 |
+
**Changes Needed:**
|
| 88 |
+
```dockerfile
|
| 89 |
+
# Update Dockerfile
|
| 90 |
+
RUN pip install --no-cache-dir vllm>=0.9.0,<0.10.0
|
| 91 |
+
|
| 92 |
+
# May need to update PyTorch:
|
| 93 |
+
RUN pip install --no-cache-dir \
|
| 94 |
+
torch>=2.5.0 \
|
| 95 |
+
--index-url https://download.pytorch.org/whl/cu124
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
### Option 2: Upgrade to vLLM 0.10.x (If CUDA 13 available)
|
| 99 |
+
|
| 100 |
+
**Why:**
|
| 101 |
+
- Latest features and optimizations
|
| 102 |
+
- Best performance improvements
|
| 103 |
+
|
| 104 |
+
**Changes Needed:**
|
| 105 |
+
```dockerfile
|
| 106 |
+
# Update base image to CUDA 13
|
| 107 |
+
FROM nvidia/cuda:13.0.2-devel-ubuntu22.04
|
| 108 |
+
|
| 109 |
+
# Update PyTorch for CUDA 13
|
| 110 |
+
RUN pip install --no-cache-dir \
|
| 111 |
+
torch>=2.5.0 \
|
| 112 |
+
--index-url https://download.pytorch.org/whl/cu130
|
| 113 |
+
|
| 114 |
+
# Install latest vLLM
|
| 115 |
+
RUN pip install --no-cache-dir vllm>=0.10.0
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
### Option 3: Gradual Upgrade (Safest)
|
| 119 |
+
|
| 120 |
+
1. **First:** Upgrade to vLLM 0.7.x or 0.8.x
|
| 121 |
+
- Test Qwen3 compatibility
|
| 122 |
+
- Verify performance
|
| 123 |
+
|
| 124 |
+
2. **Then:** Move to 0.9.x
|
| 125 |
+
- Test thoroughly
|
| 126 |
+
- Monitor stability
|
| 127 |
+
|
| 128 |
+
3. **Finally:** Consider 0.10.x if needed
|
| 129 |
+
|
| 130 |
+
## Code Changes Required
|
| 131 |
+
|
| 132 |
+
### Minimal Changes Expected
|
| 133 |
+
|
| 134 |
+
1. **Environment Variables**
|
| 135 |
+
- `VLLM_USE_V1=0` may no longer be needed (v1 engine is default in newer versions)
|
| 136 |
+
- May need to update or remove
|
| 137 |
+
|
| 138 |
+
2. **API Changes**
|
| 139 |
+
- LLM initialization likely compatible
|
| 140 |
+
- Some parameters may be deprecated
|
| 141 |
+
- Check release notes
|
| 142 |
+
|
| 143 |
+
3. **Streaming**
|
| 144 |
+
- Better streaming support in newer versions
|
| 145 |
+
- May need to update streaming implementation
|
| 146 |
+
|
| 147 |
+
## Testing Checklist
|
| 148 |
+
|
| 149 |
+
After upgrading:
|
| 150 |
+
|
| 151 |
+
- [ ] Model loads successfully
|
| 152 |
+
- [ ] Qwen3 architecture works
|
| 153 |
+
- [ ] CUDA graphs work (optimized mode)
|
| 154 |
+
- [ ] Inference produces correct results
|
| 155 |
+
- [ ] Streaming works
|
| 156 |
+
- [ ] Memory usage acceptable
|
| 157 |
+
- [ ] Performance improved/stable
|
| 158 |
+
- [ ] No regressions in API compatibility
|
| 159 |
+
|
| 160 |
+
## Recommendations
|
| 161 |
+
|
| 162 |
+
### Immediate Action: Upgrade to vLLM 0.9.x
|
| 163 |
+
|
| 164 |
+
**Reasoning:**
|
| 165 |
+
1. Still supports CUDA 12.4 (no base image change needed)
|
| 166 |
+
2. Much better than 0.6.5
|
| 167 |
+
3. Better Qwen3 support
|
| 168 |
+
4. More stable than 0.10.x
|
| 169 |
+
5. Significant improvements without breaking changes
|
| 170 |
+
|
| 171 |
+
**Steps:**
|
| 172 |
+
1. Update Dockerfile to use vLLM 0.9.2
|
| 173 |
+
2. Update PyTorch to 2.5+ (may be needed)
|
| 174 |
+
3. Test on deployment
|
| 175 |
+
4. Monitor for issues
|
| 176 |
+
|
| 177 |
+
### Future Consideration: vLLM 0.10.x
|
| 178 |
+
|
| 179 |
+
Only if:
|
| 180 |
+
- CUDA 13 becomes available
|
| 181 |
+
- Need specific 0.10.x features
|
| 182 |
+
- 0.9.x proves insufficient
|
| 183 |
+
|
| 184 |
+
## Summary
|
| 185 |
+
|
| 186 |
+
**Current:** vLLM 0.6.5 (old, but working)
|
| 187 |
+
**Recommended:** vLLM 0.9.2 (good balance)
|
| 188 |
+
**Latest:** vLLM 0.10.2 (requires CUDA 13)
|
| 189 |
+
|
| 190 |
+
**Action:** Upgrade to vLLM 0.9.2 for best compatibility with current setup while gaining significant improvements.
|
| 191 |
+
|
app/models/openai.py
CHANGED
|
@@ -11,11 +11,12 @@ class Message(BaseModel):
|
|
| 11 |
|
| 12 |
|
| 13 |
class ChatCompletionRequest(BaseModel):
|
| 14 |
-
model: str
|
| 15 |
messages: List[Message]
|
| 16 |
-
temperature: Optional[float] = 0.
|
| 17 |
-
max_tokens: Optional[int] =
|
| 18 |
stream: Optional[bool] = False
|
|
|
|
| 19 |
|
| 20 |
|
| 21 |
class ChoiceMessage(BaseModel):
|
|
|
|
| 11 |
|
| 12 |
|
| 13 |
class ChatCompletionRequest(BaseModel):
|
| 14 |
+
model: Optional[str] = None # Optional, will use default from config
|
| 15 |
messages: List[Message]
|
| 16 |
+
temperature: Optional[float] = 0.7
|
| 17 |
+
max_tokens: Optional[int] = None
|
| 18 |
stream: Optional[bool] = False
|
| 19 |
+
top_p: Optional[float] = 1.0
|
| 20 |
|
| 21 |
|
| 22 |
class ChoiceMessage(BaseModel):
|
app/providers/vllm.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
import os
|
| 2 |
-
|
|
|
|
| 3 |
from vllm import LLM, SamplingParams
|
| 4 |
-
from vllm.entrypoints.openai.api_server import build_async_engine_client
|
| 5 |
import asyncio
|
| 6 |
from huggingface_hub import login
|
| 7 |
|
|
@@ -10,60 +10,159 @@ model_name = "DragonLLM/qwen3-8b-fin-v1.0"
|
|
| 10 |
llm_engine = None
|
| 11 |
|
| 12 |
def initialize_vllm():
|
| 13 |
-
"""Initialize vLLM engine with the model
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
global llm_engine
|
| 15 |
|
| 16 |
if llm_engine is None:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
print(f"Initializing vLLM with model: {model_name}")
|
| 18 |
|
| 19 |
# Get HF token from environment (Hugging Face Space secret)
|
| 20 |
-
#
|
| 21 |
-
hf_token =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
if hf_token:
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
print(f"β
{token_source} found (length: {len(hf_token)})")
|
| 25 |
-
|
|
|
|
| 26 |
try:
|
| 27 |
login(token=hf_token, add_to_git_credential=False)
|
|
|
|
| 28 |
print("β
Successfully authenticated with Hugging Face Hub")
|
| 29 |
except Exception as e:
|
|
|
|
| 30 |
print(f"β οΈ Warning: Failed to authenticate with HF Hub: {e}")
|
| 31 |
-
|
|
|
|
|
|
|
| 32 |
os.environ["HF_TOKEN"] = hf_token
|
| 33 |
os.environ["HUGGING_FACE_HUB_TOKEN"] = hf_token
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
else:
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
try:
|
| 39 |
-
# Initialize vLLM engine
|
|
|
|
|
|
|
| 40 |
print(f"Attempting to load model: {model_name}")
|
| 41 |
-
print(f"Model type: DragonLLM Qwen3 8B (bfloat16)
|
| 42 |
print(f"Download directory: /tmp/huggingface")
|
| 43 |
print(f"Trust remote code: True")
|
| 44 |
print(f"L4 GPU: 24GB VRAM available")
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
print(f"GPU memory utilization: 0.85")
|
| 47 |
-
print(f"vLLM: v0.
|
| 48 |
-
print(f"PyTorch: 2.
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
)
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
except Exception as e:
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
raise
|
| 68 |
|
| 69 |
|
|
@@ -88,7 +187,7 @@ class VLLMProvider:
|
|
| 88 |
]
|
| 89 |
}
|
| 90 |
|
| 91 |
-
async def chat(self, payload: Dict[str, Any], stream: bool = False) -> Dict[str, Any]:
|
| 92 |
import logging
|
| 93 |
logger = logging.getLogger(__name__)
|
| 94 |
|
|
@@ -115,7 +214,11 @@ class VLLMProvider:
|
|
| 115 |
max_tokens=max_tokens,
|
| 116 |
)
|
| 117 |
|
| 118 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
outputs = llm_engine.generate([prompt], sampling_params)
|
| 120 |
|
| 121 |
# Extract the generated text
|
|
@@ -123,11 +226,16 @@ class VLLMProvider:
|
|
| 123 |
logger.info(f"Generated text: {generated_text[:100]}...")
|
| 124 |
|
| 125 |
# Build OpenAI-compatible response
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
return {
|
| 127 |
-
"id":
|
| 128 |
"object": "chat.completion",
|
| 129 |
-
"created":
|
| 130 |
-
"model": model_name,
|
| 131 |
"choices": [
|
| 132 |
{
|
| 133 |
"index": 0,
|
|
@@ -139,15 +247,92 @@ class VLLMProvider:
|
|
| 139 |
}
|
| 140 |
],
|
| 141 |
"usage": {
|
| 142 |
-
"prompt_tokens":
|
| 143 |
-
"completion_tokens":
|
| 144 |
-
"total_tokens":
|
| 145 |
}
|
| 146 |
}
|
| 147 |
except Exception as e:
|
| 148 |
logger.error(f"Error in chat completion: {str(e)}", exc_info=True)
|
| 149 |
raise
|
| 150 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
def _messages_to_prompt(self, messages: list) -> str:
|
| 152 |
"""Convert OpenAI messages format to prompt"""
|
| 153 |
prompt = ""
|
|
@@ -162,3 +347,18 @@ class VLLMProvider:
|
|
| 162 |
prompt += f"Assistant: {content}\n"
|
| 163 |
prompt += "Assistant: "
|
| 164 |
return prompt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
+
import time
|
| 3 |
+
from typing import Dict, Any, AsyncIterator, Union
|
| 4 |
from vllm import LLM, SamplingParams
|
|
|
|
| 5 |
import asyncio
|
| 6 |
from huggingface_hub import login
|
| 7 |
|
|
|
|
| 10 |
llm_engine = None
|
| 11 |
|
| 12 |
def initialize_vllm():
|
| 13 |
+
"""Initialize vLLM engine with the model
|
| 14 |
+
|
| 15 |
+
Handles authentication with Hugging Face Hub for accessing DragonLLM models.
|
| 16 |
+
Prioritizes HF_TOKEN_LC2 (DragonLLM access) over HF_TOKEN_LC.
|
| 17 |
+
"""
|
| 18 |
global llm_engine
|
| 19 |
|
| 20 |
if llm_engine is None:
|
| 21 |
+
import logging
|
| 22 |
+
logger = logging.getLogger(__name__)
|
| 23 |
+
|
| 24 |
+
logger.info(f"Initializing vLLM with model: {model_name}")
|
| 25 |
print(f"Initializing vLLM with model: {model_name}")
|
| 26 |
|
| 27 |
# Get HF token from environment (Hugging Face Space secret)
|
| 28 |
+
# Priority: HF_TOKEN_LC2 (for DragonLLM access) > HF_TOKEN_LC > HF_TOKEN
|
| 29 |
+
hf_token = (
|
| 30 |
+
os.getenv("HF_TOKEN_LC2") or
|
| 31 |
+
os.getenv("HF_TOKEN_LC") or
|
| 32 |
+
os.getenv("HF_TOKEN") or
|
| 33 |
+
os.getenv("HUGGING_FACE_HUB_TOKEN")
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
if hf_token:
|
| 37 |
+
# Determine token source for logging
|
| 38 |
+
if os.getenv("HF_TOKEN_LC2"):
|
| 39 |
+
token_source = "HF_TOKEN_LC2"
|
| 40 |
+
elif os.getenv("HF_TOKEN_LC"):
|
| 41 |
+
token_source = "HF_TOKEN_LC"
|
| 42 |
+
elif os.getenv("HF_TOKEN"):
|
| 43 |
+
token_source = "HF_TOKEN"
|
| 44 |
+
else:
|
| 45 |
+
token_source = "HUGGING_FACE_HUB_TOKEN"
|
| 46 |
+
|
| 47 |
+
logger.info(f"β
{token_source} found (length: {len(hf_token)})")
|
| 48 |
print(f"β
{token_source} found (length: {len(hf_token)})")
|
| 49 |
+
|
| 50 |
+
# Authenticate with Hugging Face Hub
|
| 51 |
try:
|
| 52 |
login(token=hf_token, add_to_git_credential=False)
|
| 53 |
+
logger.info("β
Successfully authenticated with Hugging Face Hub")
|
| 54 |
print("β
Successfully authenticated with Hugging Face Hub")
|
| 55 |
except Exception as e:
|
| 56 |
+
logger.warning(f"β οΈ Warning: Failed to authenticate with HF Hub: {e}")
|
| 57 |
print(f"β οΈ Warning: Failed to authenticate with HF Hub: {e}")
|
| 58 |
+
|
| 59 |
+
# Set all possible environment variables that vLLM/huggingface_hub might check
|
| 60 |
+
# This ensures compatibility across different versions
|
| 61 |
os.environ["HF_TOKEN"] = hf_token
|
| 62 |
os.environ["HUGGING_FACE_HUB_TOKEN"] = hf_token
|
| 63 |
+
# Some tools check for these variants too
|
| 64 |
+
os.environ["HF_API_TOKEN"] = hf_token
|
| 65 |
+
|
| 66 |
+
logger.info("β
Hugging Face token environment variables set")
|
| 67 |
else:
|
| 68 |
+
logger.warning("β οΈ WARNING: No HF token found in environment!")
|
| 69 |
+
logger.warning(f" Checked: HF_TOKEN_LC2, HF_TOKEN_LC, HF_TOKEN, HUGGING_FACE_HUB_TOKEN")
|
| 70 |
+
logger.warning(f" Available env vars: {[k for k in os.environ.keys() if 'TOKEN' in k or 'HF' in k]}")
|
| 71 |
+
print("β οΈ WARNING: No HF token found in environment!")
|
| 72 |
+
print(f" Checked: HF_TOKEN_LC2, HF_TOKEN_LC, HF_TOKEN, HUGGING_FACE_HUB_TOKEN")
|
| 73 |
+
print(f" Available env vars with 'TOKEN' or 'HF': {[k for k in os.environ.keys() if 'TOKEN' in k or 'HF' in k]}")
|
| 74 |
+
print(" β οΈ Model download may fail if DragonLLM/qwen3-8b-fin-v1.0 is gated!")
|
| 75 |
|
| 76 |
try:
|
| 77 |
+
# Initialize vLLM engine
|
| 78 |
+
# Note: vLLM 0.6.5 will use HF_TOKEN from environment for model downloads
|
| 79 |
+
logger.info(f"Attempting to load model: {model_name}")
|
| 80 |
print(f"Attempting to load model: {model_name}")
|
| 81 |
+
print(f"Model type: DragonLLM Qwen3 8B (bfloat16)")
|
| 82 |
print(f"Download directory: /tmp/huggingface")
|
| 83 |
print(f"Trust remote code: True")
|
| 84 |
print(f"L4 GPU: 24GB VRAM available")
|
| 85 |
+
|
| 86 |
+
# Try optimized mode first (CUDA graphs enabled)
|
| 87 |
+
# Falls back to eager mode if CUDA graphs fail
|
| 88 |
+
use_optimized = os.getenv("VLLM_USE_EAGER", "auto").lower()
|
| 89 |
+
if use_optimized == "true":
|
| 90 |
+
enforce_eager = True
|
| 91 |
+
mode_desc = "Eager mode (forced)"
|
| 92 |
+
elif use_optimized == "false":
|
| 93 |
+
enforce_eager = False
|
| 94 |
+
mode_desc = "Optimized mode (CUDA graphs enabled)"
|
| 95 |
+
else: # "auto" - try optimized, fallback to eager
|
| 96 |
+
enforce_eager = False
|
| 97 |
+
mode_desc = "Optimized mode (auto, fallback to eager if needed)"
|
| 98 |
+
|
| 99 |
+
print(f"Mode: {mode_desc}")
|
| 100 |
print(f"GPU memory utilization: 0.85")
|
| 101 |
+
print(f"vLLM: v0.9.2 (Latest stable, improved Qwen3 support)")
|
| 102 |
+
print(f"PyTorch: 2.5.0+ (CUDA 12.4 binary)")
|
| 103 |
+
|
| 104 |
+
# Common initialization parameters
|
| 105 |
+
init_params = {
|
| 106 |
+
"model": model_name,
|
| 107 |
+
"trust_remote_code": True,
|
| 108 |
+
"dtype": "bfloat16", # Use bfloat16 for Qwen3 (required)
|
| 109 |
+
"max_model_len": 4096, # Reduced for L4 KV cache constraints
|
| 110 |
+
"gpu_memory_utilization": 0.85, # Can use more with stable v0 engine
|
| 111 |
+
"tensor_parallel_size": 1, # Single L4 GPU
|
| 112 |
+
"download_dir": "/tmp/huggingface",
|
| 113 |
+
"tokenizer_mode": "auto",
|
| 114 |
+
"disable_log_stats": False, # Enable logging for debugging
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
# Try optimized mode first (unless explicitly disabled)
|
| 118 |
+
if use_optimized == "auto" or use_optimized == "false":
|
| 119 |
+
try:
|
| 120 |
+
print(f"π Attempting optimized mode with CUDA graphs...")
|
| 121 |
+
logger.info("Attempting optimized mode (enforce_eager=False)")
|
| 122 |
+
init_params["enforce_eager"] = False
|
| 123 |
+
llm_engine = LLM(**init_params)
|
| 124 |
+
print(f"β
vLLM engine initialized successfully in OPTIMIZED mode!")
|
| 125 |
+
logger.info("β
vLLM engine initialized in optimized mode (CUDA graphs enabled)")
|
| 126 |
+
except Exception as opt_error:
|
| 127 |
+
error_msg = str(opt_error).lower()
|
| 128 |
+
# Check if error is CUDA graph related
|
| 129 |
+
if "cuda graph" in error_msg or "graph" in error_msg or use_optimized == "auto":
|
| 130 |
+
logger.warning(f"β οΈ Optimized mode failed, falling back to eager mode: {opt_error}")
|
| 131 |
+
print(f"β οΈ Optimized mode failed: {opt_error}")
|
| 132 |
+
print(f"π Falling back to eager mode for stability...")
|
| 133 |
+
init_params["enforce_eager"] = True
|
| 134 |
+
llm_engine = LLM(**init_params)
|
| 135 |
+
print(f"β
vLLM engine initialized successfully in EAGER mode (fallback)")
|
| 136 |
+
logger.info("β
vLLM engine initialized in eager mode (fallback after optimized mode failure)")
|
| 137 |
+
else:
|
| 138 |
+
# Re-raise if it's not a CUDA graph issue or if optimized is forced
|
| 139 |
+
raise
|
| 140 |
+
else:
|
| 141 |
+
# Eager mode explicitly requested
|
| 142 |
+
print(f"βοΈ Using eager mode (explicitly requested)")
|
| 143 |
+
logger.info("Using eager mode (VLLM_USE_EAGER=true)")
|
| 144 |
+
init_params["enforce_eager"] = True
|
| 145 |
+
llm_engine = LLM(**init_params)
|
| 146 |
+
print(f"β
vLLM engine initialized successfully in EAGER mode!")
|
| 147 |
+
logger.info("β
vLLM engine initialized in eager mode")
|
| 148 |
+
|
| 149 |
except Exception as e:
|
| 150 |
+
error_msg = f"β Error initializing vLLM: {e}"
|
| 151 |
+
logger.error(error_msg, exc_info=True)
|
| 152 |
+
print(error_msg)
|
| 153 |
+
|
| 154 |
+
# Provide helpful error message for authentication issues
|
| 155 |
+
if "401" in str(e) or "Unauthorized" in str(e) or "authentication" in str(e).lower():
|
| 156 |
+
print("\nπ Authentication Error Detected!")
|
| 157 |
+
print(" This usually means:")
|
| 158 |
+
print(" 1. HF_TOKEN_LC2 is missing or invalid")
|
| 159 |
+
print(" 2. You haven't accepted the model's terms on Hugging Face")
|
| 160 |
+
print(" 3. The token doesn't have access to DragonLLM models")
|
| 161 |
+
print("\n To fix:")
|
| 162 |
+
print(" 1. Visit: https://huggingface.co/DragonLLM/qwen3-8b-fin-v1.0")
|
| 163 |
+
print(" 2. Accept the model's terms of use")
|
| 164 |
+
print(" 3. Ensure HF_TOKEN_LC2 is set as a secret in your HF Space")
|
| 165 |
+
|
| 166 |
raise
|
| 167 |
|
| 168 |
|
|
|
|
| 187 |
]
|
| 188 |
}
|
| 189 |
|
| 190 |
+
async def chat(self, payload: Dict[str, Any], stream: bool = False) -> Union[Dict[str, Any], AsyncIterator[str]]:
|
| 191 |
import logging
|
| 192 |
logger = logging.getLogger(__name__)
|
| 193 |
|
|
|
|
| 214 |
max_tokens=max_tokens,
|
| 215 |
)
|
| 216 |
|
| 217 |
+
# Handle streaming vs non-streaming
|
| 218 |
+
if stream:
|
| 219 |
+
return self._chat_stream(prompt, sampling_params, payload.get("model", model_name))
|
| 220 |
+
|
| 221 |
+
# Generate response using vLLM (non-streaming)
|
| 222 |
outputs = llm_engine.generate([prompt], sampling_params)
|
| 223 |
|
| 224 |
# Extract the generated text
|
|
|
|
| 226 |
logger.info(f"Generated text: {generated_text[:100]}...")
|
| 227 |
|
| 228 |
# Build OpenAI-compatible response
|
| 229 |
+
completion_id = f"chatcmpl-{os.urandom(12).hex()}"
|
| 230 |
+
created = int(time.time())
|
| 231 |
+
prompt_tokens = len(outputs[0].prompt_token_ids)
|
| 232 |
+
completion_tokens = len(outputs[0].outputs[0].token_ids)
|
| 233 |
+
|
| 234 |
return {
|
| 235 |
+
"id": completion_id,
|
| 236 |
"object": "chat.completion",
|
| 237 |
+
"created": created,
|
| 238 |
+
"model": payload.get("model", model_name),
|
| 239 |
"choices": [
|
| 240 |
{
|
| 241 |
"index": 0,
|
|
|
|
| 247 |
}
|
| 248 |
],
|
| 249 |
"usage": {
|
| 250 |
+
"prompt_tokens": prompt_tokens,
|
| 251 |
+
"completion_tokens": completion_tokens,
|
| 252 |
+
"total_tokens": prompt_tokens + completion_tokens
|
| 253 |
}
|
| 254 |
}
|
| 255 |
except Exception as e:
|
| 256 |
logger.error(f"Error in chat completion: {str(e)}", exc_info=True)
|
| 257 |
raise
|
| 258 |
|
| 259 |
+
async def _chat_stream(self, prompt: str, sampling_params: SamplingParams, model: str) -> AsyncIterator[str]:
|
| 260 |
+
"""Stream chat completions using vLLM
|
| 261 |
+
|
| 262 |
+
Note: vLLM 0.6.5 with synchronous LLM doesn't support true streaming.
|
| 263 |
+
This implementation generates the full response and yields it in chunks
|
| 264 |
+
for OpenAI API compatibility. For true streaming, use AsyncLLMEngine.
|
| 265 |
+
"""
|
| 266 |
+
import logging
|
| 267 |
+
logger = logging.getLogger(__name__)
|
| 268 |
+
|
| 269 |
+
completion_id = f"chatcmpl-{os.urandom(12).hex()}"
|
| 270 |
+
created = int(time.time())
|
| 271 |
+
|
| 272 |
+
# Generate response (non-streaming backend, but we'll chunk it)
|
| 273 |
+
# Run in thread pool to avoid blocking
|
| 274 |
+
loop = asyncio.get_event_loop()
|
| 275 |
+
outputs = await loop.run_in_executor(
|
| 276 |
+
None,
|
| 277 |
+
lambda: llm_engine.generate([prompt], sampling_params)
|
| 278 |
+
)
|
| 279 |
+
|
| 280 |
+
generated_text = outputs[0].outputs[0].text
|
| 281 |
+
finish_reason = outputs[0].outputs[0].finish_reason or "stop"
|
| 282 |
+
|
| 283 |
+
# Yield text in chunks (simulate streaming)
|
| 284 |
+
# Split into reasonable chunks (words or characters)
|
| 285 |
+
chunk_size = 10 # words per chunk
|
| 286 |
+
words = generated_text.split()
|
| 287 |
+
|
| 288 |
+
for i in range(0, len(words), chunk_size):
|
| 289 |
+
chunk_words = words[i:i + chunk_size]
|
| 290 |
+
delta_text = " ".join(chunk_words)
|
| 291 |
+
if i + chunk_size < len(words):
|
| 292 |
+
delta_text += " "
|
| 293 |
+
|
| 294 |
+
# Format as OpenAI SSE stream chunk
|
| 295 |
+
chunk = {
|
| 296 |
+
"id": completion_id,
|
| 297 |
+
"object": "chat.completion.chunk",
|
| 298 |
+
"created": created,
|
| 299 |
+
"model": model,
|
| 300 |
+
"choices": [
|
| 301 |
+
{
|
| 302 |
+
"index": 0,
|
| 303 |
+
"delta": {
|
| 304 |
+
"content": delta_text
|
| 305 |
+
},
|
| 306 |
+
"finish_reason": None
|
| 307 |
+
}
|
| 308 |
+
]
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
yield f"data: {self._json_dumps(chunk)}\n\n"
|
| 312 |
+
await asyncio.sleep(0) # Yield control
|
| 313 |
+
|
| 314 |
+
# Send final chunk with finish_reason
|
| 315 |
+
final_chunk = {
|
| 316 |
+
"id": completion_id,
|
| 317 |
+
"object": "chat.completion.chunk",
|
| 318 |
+
"created": created,
|
| 319 |
+
"model": model,
|
| 320 |
+
"choices": [
|
| 321 |
+
{
|
| 322 |
+
"index": 0,
|
| 323 |
+
"delta": {},
|
| 324 |
+
"finish_reason": finish_reason
|
| 325 |
+
}
|
| 326 |
+
]
|
| 327 |
+
}
|
| 328 |
+
yield f"data: {self._json_dumps(final_chunk)}\n\n"
|
| 329 |
+
yield "data: [DONE]\n\n"
|
| 330 |
+
|
| 331 |
+
def _json_dumps(self, obj: Dict[str, Any]) -> str:
|
| 332 |
+
"""JSON dump helper"""
|
| 333 |
+
import json
|
| 334 |
+
return json.dumps(obj, ensure_ascii=False)
|
| 335 |
+
|
| 336 |
def _messages_to_prompt(self, messages: list) -> str:
|
| 337 |
"""Convert OpenAI messages format to prompt"""
|
| 338 |
prompt = ""
|
|
|
|
| 347 |
prompt += f"Assistant: {content}\n"
|
| 348 |
prompt += "Assistant: "
|
| 349 |
return prompt
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
# Module-level provider instance for backward compatibility
|
| 353 |
+
_provider = VLLMProvider()
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
# Module-level functions for direct import
|
| 357 |
+
async def list_models() -> Dict[str, Any]:
|
| 358 |
+
"""List available models"""
|
| 359 |
+
return await _provider.list_models()
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
async def chat(payload: Dict[str, Any], stream: bool = False) -> Union[Dict[str, Any], AsyncIterator[str]]:
|
| 363 |
+
"""Chat completion"""
|
| 364 |
+
return await _provider.chat(payload, stream=stream)
|
app/routers/openai_api.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
-
import time
|
| 2 |
from typing import Any, Dict
|
|
|
|
| 3 |
|
| 4 |
from fastapi import APIRouter
|
| 5 |
from fastapi.responses import StreamingResponse, JSONResponse
|
|
@@ -8,49 +8,45 @@ from app.config import settings
|
|
| 8 |
from app.models.openai import ChatCompletionRequest
|
| 9 |
from app.services import chat_service
|
| 10 |
|
|
|
|
| 11 |
|
| 12 |
router = APIRouter()
|
| 13 |
|
| 14 |
|
| 15 |
@router.get("/models")
|
| 16 |
async def list_models():
|
|
|
|
| 17 |
return await chat_service.list_models()
|
| 18 |
|
| 19 |
|
| 20 |
@router.post("/chat/completions")
|
| 21 |
async def chat_completions(body: ChatCompletionRequest):
|
| 22 |
-
|
| 23 |
-
logger = logging.getLogger(__name__)
|
| 24 |
-
|
| 25 |
try:
|
|
|
|
| 26 |
payload: Dict[str, Any] = {
|
| 27 |
"model": body.model or settings.model,
|
| 28 |
"messages": [m.model_dump() for m in body.messages],
|
| 29 |
-
"temperature": body.temperature,
|
| 30 |
-
|
| 31 |
"stream": body.stream or False,
|
| 32 |
}
|
| 33 |
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
if body.stream:
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
async for line in upstream.aiter_lines():
|
| 41 |
-
if not line:
|
| 42 |
-
continue
|
| 43 |
-
if line.startswith("data:"):
|
| 44 |
-
yield f"{line}\n\n"
|
| 45 |
-
else:
|
| 46 |
-
yield f"data: {line}\n\n"
|
| 47 |
-
|
| 48 |
-
return StreamingResponse(event_stream(), media_type="text/event-stream")
|
| 49 |
|
|
|
|
| 50 |
data = await chat_service.chat(payload, stream=False)
|
| 51 |
-
# Assume vLLM already returns OpenAI-compatible schema; pass through.
|
| 52 |
-
# If needed, normalize here.
|
| 53 |
return JSONResponse(content=data)
|
|
|
|
| 54 |
except Exception as e:
|
| 55 |
logger.error(f"Error in chat completions endpoint: {str(e)}", exc_info=True)
|
| 56 |
return JSONResponse(
|
|
|
|
|
|
|
| 1 |
from typing import Any, Dict
|
| 2 |
+
import logging
|
| 3 |
|
| 4 |
from fastapi import APIRouter
|
| 5 |
from fastapi.responses import StreamingResponse, JSONResponse
|
|
|
|
| 8 |
from app.models.openai import ChatCompletionRequest
|
| 9 |
from app.services import chat_service
|
| 10 |
|
| 11 |
+
logger = logging.getLogger(__name__)
|
| 12 |
|
| 13 |
router = APIRouter()
|
| 14 |
|
| 15 |
|
| 16 |
@router.get("/models")
|
| 17 |
async def list_models():
|
| 18 |
+
"""List available models (OpenAI-compatible endpoint)"""
|
| 19 |
return await chat_service.list_models()
|
| 20 |
|
| 21 |
|
| 22 |
@router.post("/chat/completions")
|
| 23 |
async def chat_completions(body: ChatCompletionRequest):
|
| 24 |
+
"""Chat completions endpoint (OpenAI-compatible)"""
|
|
|
|
|
|
|
| 25 |
try:
|
| 26 |
+
# Build payload with all supported parameters
|
| 27 |
payload: Dict[str, Any] = {
|
| 28 |
"model": body.model or settings.model,
|
| 29 |
"messages": [m.model_dump() for m in body.messages],
|
| 30 |
+
"temperature": body.temperature or 0.7,
|
| 31 |
+
"top_p": body.top_p or 1.0,
|
| 32 |
"stream": body.stream or False,
|
| 33 |
}
|
| 34 |
|
| 35 |
+
# Add optional max_tokens if provided
|
| 36 |
+
if body.max_tokens is not None:
|
| 37 |
+
payload["max_tokens"] = body.max_tokens
|
| 38 |
+
|
| 39 |
+
logger.info(f"Chat completion request: model={payload['model']}, messages={len(payload['messages'])}, stream={payload['stream']}")
|
| 40 |
|
| 41 |
if body.stream:
|
| 42 |
+
stream = await chat_service.chat(payload, stream=True)
|
| 43 |
+
# stream is already an AsyncIterator[str] with SSE-formatted chunks
|
| 44 |
+
return StreamingResponse(stream, media_type="text/event-stream")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
+
# Non-streaming response
|
| 47 |
data = await chat_service.chat(payload, stream=False)
|
|
|
|
|
|
|
| 48 |
return JSONResponse(content=data)
|
| 49 |
+
|
| 50 |
except Exception as e:
|
| 51 |
logger.error(f"Error in chat completions endpoint: {str(e)}", exc_info=True)
|
| 52 |
return JSONResponse(
|
app/services/chat_service.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
| 1 |
from typing import Any, Dict
|
| 2 |
-
from app.providers.vllm import VLLMProvider
|
| 3 |
|
| 4 |
-
|
| 5 |
-
|
| 6 |
|
| 7 |
async def list_models() -> Dict[str, Any]:
|
| 8 |
return await provider.list_models()
|
| 9 |
|
|
|
|
| 10 |
async def chat(payload: Dict[str, Any], stream: bool = False):
|
| 11 |
return await provider.chat(payload, stream=stream)
|
| 12 |
|
|
|
|
| 1 |
from typing import Any, Dict
|
|
|
|
| 2 |
|
| 3 |
+
from app.providers import vllm as provider
|
| 4 |
+
|
| 5 |
|
| 6 |
async def list_models() -> Dict[str, Any]:
|
| 7 |
return await provider.list_models()
|
| 8 |
|
| 9 |
+
|
| 10 |
async def chat(payload: Dict[str, Any], stream: bool = False):
|
| 11 |
return await provider.chat(payload, stream=stream)
|
| 12 |
|
requirements-dev.txt
CHANGED
|
@@ -6,6 +6,9 @@ pytest>=7.4.0
|
|
| 6 |
pytest-asyncio>=0.21.0
|
| 7 |
openai>=1.0.0
|
| 8 |
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
| 11 |
|
|
|
|
| 6 |
pytest-asyncio>=0.21.0
|
| 7 |
openai>=1.0.0
|
| 8 |
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
|
| 14 |
|
requirements.txt
CHANGED
|
@@ -1,5 +1,8 @@
|
|
| 1 |
-
#
|
| 2 |
-
|
|
|
|
|
|
|
|
|
|
| 3 |
fastapi>=0.115.0
|
| 4 |
uvicorn[standard]>=0.30.0
|
| 5 |
pydantic>=2.8.0
|
|
@@ -8,4 +11,5 @@ httpx>=0.27.0
|
|
| 8 |
python-dotenv>=1.0.1
|
| 9 |
tenacity>=8.3.0
|
| 10 |
PyMuPDF>=1.24.0
|
| 11 |
-
|
|
|
|
|
|
| 1 |
+
# Core dependencies for OpenAI-compatible API service
|
| 2 |
+
# Note: vLLM and PyTorch are installed separately in Dockerfile for CUDA support
|
| 3 |
+
# vllm==0.6.5 # Installed in Dockerfile
|
| 4 |
+
# torch==2.4.0 # Installed in Dockerfile
|
| 5 |
+
|
| 6 |
fastapi>=0.115.0
|
| 7 |
uvicorn[standard]>=0.30.0
|
| 8 |
pydantic>=2.8.0
|
|
|
|
| 11 |
python-dotenv>=1.0.1
|
| 12 |
tenacity>=8.3.0
|
| 13 |
PyMuPDF>=1.24.0
|
| 14 |
+
python-multipart>=0.0.6
|
| 15 |
+
huggingface-hub>=0.20.0
|
scripts/check_vllm_compatibility.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Check compatibility between DragonLLM/qwen3-8b-fin-v1.0 and vLLM 0.6.5
|
| 4 |
+
|
| 5 |
+
This script verifies:
|
| 6 |
+
1. vLLM version installed
|
| 7 |
+
2. Model architecture support
|
| 8 |
+
3. Configuration compatibility
|
| 9 |
+
4. Known issues or limitations
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import sys
|
| 13 |
+
import subprocess
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
|
| 16 |
+
# Add parent directory to path
|
| 17 |
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
| 18 |
+
|
| 19 |
+
try:
|
| 20 |
+
import vllm
|
| 21 |
+
from vllm import LLM
|
| 22 |
+
from vllm.model_executor.models import MODEL_REGISTRY
|
| 23 |
+
except ImportError:
|
| 24 |
+
print("β Error: vLLM not installed")
|
| 25 |
+
print(" Install it with: pip install vllm==0.6.5")
|
| 26 |
+
sys.exit(1)
|
| 27 |
+
|
| 28 |
+
try:
|
| 29 |
+
from huggingface_hub import model_info
|
| 30 |
+
from huggingface_hub.utils import HfHubHTTPError
|
| 31 |
+
except ImportError:
|
| 32 |
+
print("β οΈ Warning: huggingface_hub not installed")
|
| 33 |
+
print(" Some checks will be skipped")
|
| 34 |
+
model_info = None
|
| 35 |
+
|
| 36 |
+
MODEL_NAME = "DragonLLM/qwen3-8b-fin-v1.0"
|
| 37 |
+
VLLM_VERSION = "0.6.5"
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def check_vllm_version():
|
| 41 |
+
"""Check installed vLLM version"""
|
| 42 |
+
print("\n" + "="*70)
|
| 43 |
+
print("CHECK 1: vLLM Version")
|
| 44 |
+
print("="*70)
|
| 45 |
+
|
| 46 |
+
installed_version = vllm.__version__
|
| 47 |
+
print(f"Installed vLLM version: {installed_version}")
|
| 48 |
+
print(f"Expected version: {VLLM_VERSION}")
|
| 49 |
+
|
| 50 |
+
if installed_version == VLLM_VERSION:
|
| 51 |
+
print("β
Version matches!")
|
| 52 |
+
return True
|
| 53 |
+
elif installed_version.startswith("0.6"):
|
| 54 |
+
print(f"β οΈ Version mismatch: {installed_version} (expected {VLLM_VERSION})")
|
| 55 |
+
print(" This should be compatible but may have differences")
|
| 56 |
+
return True
|
| 57 |
+
else:
|
| 58 |
+
print(f"β Version mismatch: {installed_version}")
|
| 59 |
+
print(f" This may cause compatibility issues")
|
| 60 |
+
return False
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def check_model_registry():
|
| 64 |
+
"""Check if Qwen3 is in vLLM's model registry"""
|
| 65 |
+
print("\n" + "="*70)
|
| 66 |
+
print("CHECK 2: Model Architecture Support")
|
| 67 |
+
print("="*70)
|
| 68 |
+
|
| 69 |
+
# Get all registered models
|
| 70 |
+
registered_models = list(MODEL_REGISTRY.keys())
|
| 71 |
+
|
| 72 |
+
# Look for Qwen variants
|
| 73 |
+
qwen_models = [m for m in registered_models if 'qwen' in m.lower()]
|
| 74 |
+
|
| 75 |
+
print(f"Total models in registry: {len(registered_models)}")
|
| 76 |
+
print(f"Qwen-related models found: {len(qwen_models)}")
|
| 77 |
+
|
| 78 |
+
if qwen_models:
|
| 79 |
+
print("\nβ
Qwen models found in registry:")
|
| 80 |
+
for model in sorted(qwen_models):
|
| 81 |
+
print(f" - {model}")
|
| 82 |
+
|
| 83 |
+
# Check specifically for Qwen3
|
| 84 |
+
qwen3_models = [m for m in qwen_models if 'qwen3' in m.lower() or '3' in m]
|
| 85 |
+
if qwen3_models:
|
| 86 |
+
print("\nβ
Qwen3 support detected!")
|
| 87 |
+
for model in qwen3_models:
|
| 88 |
+
print(f" - {model}")
|
| 89 |
+
return True
|
| 90 |
+
else:
|
| 91 |
+
print("\nβ οΈ Qwen models found but Qwen3 specifically not detected")
|
| 92 |
+
print(" Qwen3 might be handled by a generic Qwen loader")
|
| 93 |
+
return True # Still likely compatible
|
| 94 |
+
else:
|
| 95 |
+
print("\nβ No Qwen models found in registry")
|
| 96 |
+
print(" This suggests Qwen3 may not be supported")
|
| 97 |
+
return False
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def check_model_info():
|
| 101 |
+
"""Check model information from Hugging Face"""
|
| 102 |
+
print("\n" + "="*70)
|
| 103 |
+
print("CHECK 3: Model Information")
|
| 104 |
+
print("="*70)
|
| 105 |
+
|
| 106 |
+
if not model_info:
|
| 107 |
+
print("β οΈ Skipping (huggingface_hub not available)")
|
| 108 |
+
return None
|
| 109 |
+
|
| 110 |
+
try:
|
| 111 |
+
info = model_info(MODEL_NAME, token=True)
|
| 112 |
+
print(f"Model: {MODEL_NAME}")
|
| 113 |
+
print(f"Architecture: {info.config.get('architectures', ['Unknown'])[0] if hasattr(info, 'config') else 'qwen3'}")
|
| 114 |
+
|
| 115 |
+
# Check model config
|
| 116 |
+
if hasattr(info, 'config') and info.config:
|
| 117 |
+
config = info.config
|
| 118 |
+
print(f"\nModel Configuration:")
|
| 119 |
+
|
| 120 |
+
# Check for Qwen-specific config
|
| 121 |
+
if 'qwen' in str(config).lower():
|
| 122 |
+
print(" β
Qwen architecture detected in config")
|
| 123 |
+
|
| 124 |
+
# Check for required fields
|
| 125 |
+
if hasattr(config, 'torch_dtype') or 'torch_dtype' in str(config):
|
| 126 |
+
print(f" β
torch_dtype found")
|
| 127 |
+
|
| 128 |
+
if 'bfloat16' in str(config).lower():
|
| 129 |
+
print(f" β
bfloat16 support confirmed")
|
| 130 |
+
|
| 131 |
+
return True
|
| 132 |
+
|
| 133 |
+
except HfHubHTTPError as e:
|
| 134 |
+
if e.response.status_code == 401:
|
| 135 |
+
print(f"β Unauthorized: Need to accept model terms")
|
| 136 |
+
print(f" Visit: https://huggingface.co/{MODEL_NAME}")
|
| 137 |
+
return False
|
| 138 |
+
else:
|
| 139 |
+
print(f"β Error accessing model: {e}")
|
| 140 |
+
return False
|
| 141 |
+
except Exception as e:
|
| 142 |
+
print(f"β οΈ Could not fetch model info: {e}")
|
| 143 |
+
return None
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def check_configuration():
|
| 147 |
+
"""Check if the configuration used is compatible"""
|
| 148 |
+
print("\n" + "="*70)
|
| 149 |
+
print("CHECK 4: Configuration Compatibility")
|
| 150 |
+
print("="*70)
|
| 151 |
+
|
| 152 |
+
print("Current configuration:")
|
| 153 |
+
print(f" - dtype: bfloat16")
|
| 154 |
+
print(f" - trust_remote_code: True")
|
| 155 |
+
print(f" - enforce_eager: True")
|
| 156 |
+
print(f" - max_model_len: 4096")
|
| 157 |
+
|
| 158 |
+
# Check if bfloat16 is supported
|
| 159 |
+
try:
|
| 160 |
+
import torch
|
| 161 |
+
if torch.cuda.is_bf16_supported():
|
| 162 |
+
print(" β
CUDA supports bfloat16")
|
| 163 |
+
else:
|
| 164 |
+
print(" β οΈ CUDA may not fully support bfloat16")
|
| 165 |
+
except Exception:
|
| 166 |
+
pass
|
| 167 |
+
|
| 168 |
+
print("\nβ
Configuration looks compatible")
|
| 169 |
+
print(" - bfloat16: Required for Qwen3")
|
| 170 |
+
print(" - trust_remote_code: Required for custom architectures")
|
| 171 |
+
print(" - enforce_eager: Recommended for stability")
|
| 172 |
+
|
| 173 |
+
return True
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def check_known_issues():
|
| 177 |
+
"""Check for known compatibility issues"""
|
| 178 |
+
print("\n" + "="*70)
|
| 179 |
+
print("CHECK 5: Known Issues / Compatibility Notes")
|
| 180 |
+
print("="*70)
|
| 181 |
+
|
| 182 |
+
print("Known considerations for Qwen3 + vLLM 0.6.5:")
|
| 183 |
+
print(" β
VLLM_USE_V1=0: Using v0 engine (more stable)")
|
| 184 |
+
print(" β
enforce_eager=True: Avoids CUDA graph issues")
|
| 185 |
+
print(" β
bfloat16: Required dtype for Qwen3")
|
| 186 |
+
print(" β
trust_remote_code: Required for custom tokenizers")
|
| 187 |
+
|
| 188 |
+
print("\nβ οΈ Potential Issues:")
|
| 189 |
+
print(" - Qwen3 may require newer vLLM version (check if issues occur)")
|
| 190 |
+
print(" - If model fails to load, may need vLLM 0.6.6+ or 0.7.0+")
|
| 191 |
+
print(" - Monitor for tokenizer compatibility issues")
|
| 192 |
+
|
| 193 |
+
return True
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def main():
|
| 197 |
+
"""Run all compatibility checks"""
|
| 198 |
+
print("\n" + "#"*70)
|
| 199 |
+
print("# vLLM 0.6.5 + DragonLLM/qwen3-8b-fin-v1.0 Compatibility Check")
|
| 200 |
+
print("#"*70)
|
| 201 |
+
|
| 202 |
+
results = {}
|
| 203 |
+
|
| 204 |
+
# Check 1: Version
|
| 205 |
+
results['version'] = check_vllm_version()
|
| 206 |
+
|
| 207 |
+
# Check 2: Model registry
|
| 208 |
+
results['registry'] = check_model_registry()
|
| 209 |
+
|
| 210 |
+
# Check 3: Model info
|
| 211 |
+
results['model_info'] = check_model_info()
|
| 212 |
+
|
| 213 |
+
# Check 4: Configuration
|
| 214 |
+
results['configuration'] = check_configuration()
|
| 215 |
+
|
| 216 |
+
# Check 5: Known issues
|
| 217 |
+
results['known_issues'] = check_known_issues()
|
| 218 |
+
|
| 219 |
+
# Summary
|
| 220 |
+
print("\n" + "="*70)
|
| 221 |
+
print("SUMMARY")
|
| 222 |
+
print("="*70)
|
| 223 |
+
|
| 224 |
+
for check_name, success in results.items():
|
| 225 |
+
if success is None:
|
| 226 |
+
status = "β οΈ SKIP"
|
| 227 |
+
else:
|
| 228 |
+
status = "β
PASS" if success else "β FAIL"
|
| 229 |
+
check_display = check_name.replace('_', ' ').title()
|
| 230 |
+
print(f"{status} - {check_display}")
|
| 231 |
+
|
| 232 |
+
passed = sum(1 for v in results.values() if v is True)
|
| 233 |
+
total = sum(1 for v in results.values() if v is not None)
|
| 234 |
+
|
| 235 |
+
print(f"\nResults: {passed}/{total} checks passed")
|
| 236 |
+
|
| 237 |
+
if results.get('version') and results.get('registry'):
|
| 238 |
+
print("\nβ
Basic compatibility looks good!")
|
| 239 |
+
print(" The model should work with vLLM 0.6.5")
|
| 240 |
+
print("\n If you encounter issues:")
|
| 241 |
+
print(" 1. Ensure HF_TOKEN_LC2 is set")
|
| 242 |
+
print(" 2. Check model repository access")
|
| 243 |
+
print(" 3. Verify CUDA/bfloat16 support")
|
| 244 |
+
print(" 4. Consider upgrading to vLLM 0.6.6+ if problems persist")
|
| 245 |
+
elif results.get('registry') == False:
|
| 246 |
+
print("\nβ οΈ Qwen3 may not be explicitly supported in vLLM 0.6.5")
|
| 247 |
+
print(" Consider:")
|
| 248 |
+
print(" 1. Testing with the model anyway (might still work)")
|
| 249 |
+
print(" 2. Upgrading to vLLM 0.6.6 or 0.7.0+")
|
| 250 |
+
print(" 3. Using a different model if compatibility issues occur")
|
| 251 |
+
else:
|
| 252 |
+
print("\nβ οΈ Some compatibility concerns detected")
|
| 253 |
+
print(" Review the checks above for details")
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
if __name__ == "__main__":
|
| 257 |
+
main()
|
| 258 |
+
|
scripts/query_with_context.py
DELETED
|
@@ -1,179 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Query LLM with PRIIPS Document Context
|
| 4 |
-
|
| 5 |
-
Loads extracted PRIIPS documents and queries the LLM with RAG context.
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
import sys
|
| 9 |
-
import json
|
| 10 |
-
import argparse
|
| 11 |
-
from pathlib import Path
|
| 12 |
-
from typing import List, Dict
|
| 13 |
-
import requests
|
| 14 |
-
|
| 15 |
-
# Configuration
|
| 16 |
-
BASE_URL = "https://jeanbaptdzd-priips-llm-service.hf.space"
|
| 17 |
-
MODEL = "DragonLLM/qwen3-8b-fin-v1.0"
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
def load_extracted_documents(extracted_dir: Path) -> List[Dict]:
|
| 21 |
-
"""Load all extracted PRIIPS documents."""
|
| 22 |
-
documents = []
|
| 23 |
-
|
| 24 |
-
for json_file in extracted_dir.glob("*_extracted.json"):
|
| 25 |
-
if json_file.name.startswith("_"):
|
| 26 |
-
continue # Skip summary files
|
| 27 |
-
|
| 28 |
-
with open(json_file, "r", encoding="utf-8") as f:
|
| 29 |
-
documents.append(json.load(f))
|
| 30 |
-
|
| 31 |
-
return documents
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
def build_context(documents: List[Dict], query: str, max_chars: int = 2000) -> str:
|
| 35 |
-
"""
|
| 36 |
-
Build RAG context from documents relevant to the query.
|
| 37 |
-
|
| 38 |
-
Simple implementation: include all document summaries.
|
| 39 |
-
Can be enhanced with semantic search/embeddings.
|
| 40 |
-
"""
|
| 41 |
-
context_parts = []
|
| 42 |
-
total_chars = 0
|
| 43 |
-
|
| 44 |
-
for doc in documents:
|
| 45 |
-
metadata = doc["metadata"]
|
| 46 |
-
|
| 47 |
-
# Build a summary of this document
|
| 48 |
-
doc_summary = f"\n--- Document: {metadata['product_name']} (ISIN: {metadata['isin']}) ---\n"
|
| 49 |
-
|
| 50 |
-
# Include extracted sections
|
| 51 |
-
if "sections" in doc and doc["sections"]:
|
| 52 |
-
for section_name, content in doc["sections"].items():
|
| 53 |
-
if content:
|
| 54 |
-
section_text = f"\n{section_name.upper()}:\n{content[:300]}...\n"
|
| 55 |
-
doc_summary += section_text
|
| 56 |
-
|
| 57 |
-
# Check if we have space
|
| 58 |
-
if total_chars + len(doc_summary) > max_chars:
|
| 59 |
-
break
|
| 60 |
-
|
| 61 |
-
context_parts.append(doc_summary)
|
| 62 |
-
total_chars += len(doc_summary)
|
| 63 |
-
|
| 64 |
-
if not context_parts:
|
| 65 |
-
return "No relevant documents found."
|
| 66 |
-
|
| 67 |
-
return "\n".join(context_parts)
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
def query_llm(query: str, context: str, max_tokens: int = 500) -> str:
|
| 71 |
-
"""Query the LLM with context."""
|
| 72 |
-
|
| 73 |
-
# Build the prompt with context
|
| 74 |
-
prompt = f"""You are a financial expert assistant specializing in PRIIPS Key Information Documents.
|
| 75 |
-
|
| 76 |
-
Use the following context from PRIIPS documents to answer the question:
|
| 77 |
-
|
| 78 |
-
{context}
|
| 79 |
-
|
| 80 |
-
Question: {query}
|
| 81 |
-
|
| 82 |
-
Provide a clear, accurate answer based on the context provided. If the context doesn't contain enough information, say so."""
|
| 83 |
-
|
| 84 |
-
payload = {
|
| 85 |
-
"model": MODEL,
|
| 86 |
-
"messages": [
|
| 87 |
-
{"role": "system", "content": "You are a PRIIPS financial document expert."},
|
| 88 |
-
{"role": "user", "content": prompt}
|
| 89 |
-
],
|
| 90 |
-
"max_tokens": max_tokens,
|
| 91 |
-
"temperature": 0.3 # Lower temperature for more factual responses
|
| 92 |
-
}
|
| 93 |
-
|
| 94 |
-
print(f"π Querying LLM with {len(context)} chars of context...")
|
| 95 |
-
|
| 96 |
-
try:
|
| 97 |
-
response = requests.post(
|
| 98 |
-
f"{BASE_URL}/v1/chat/completions",
|
| 99 |
-
json=payload,
|
| 100 |
-
timeout=60
|
| 101 |
-
)
|
| 102 |
-
response.raise_for_status()
|
| 103 |
-
|
| 104 |
-
data = response.json()
|
| 105 |
-
answer = data["choices"][0]["message"]["content"]
|
| 106 |
-
|
| 107 |
-
# Print usage stats
|
| 108 |
-
usage = data.get("usage", {})
|
| 109 |
-
print(f"π Tokens used: {usage.get('total_tokens', 'N/A')}")
|
| 110 |
-
|
| 111 |
-
return answer
|
| 112 |
-
|
| 113 |
-
except Exception as e:
|
| 114 |
-
return f"Error querying LLM: {e}"
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
def main():
|
| 118 |
-
parser = argparse.ArgumentParser(
|
| 119 |
-
description="Query LLM with PRIIPS document context"
|
| 120 |
-
)
|
| 121 |
-
parser.add_argument(
|
| 122 |
-
"query",
|
| 123 |
-
type=str,
|
| 124 |
-
help="Question to ask about PRIIPS documents"
|
| 125 |
-
)
|
| 126 |
-
parser.add_argument(
|
| 127 |
-
"--extracted-dir",
|
| 128 |
-
type=str,
|
| 129 |
-
default="priips_documents/extracted",
|
| 130 |
-
help="Directory containing extracted documents"
|
| 131 |
-
)
|
| 132 |
-
parser.add_argument(
|
| 133 |
-
"--max-context",
|
| 134 |
-
type=int,
|
| 135 |
-
default=2000,
|
| 136 |
-
help="Maximum context characters to include"
|
| 137 |
-
)
|
| 138 |
-
parser.add_argument(
|
| 139 |
-
"--max-tokens",
|
| 140 |
-
type=int,
|
| 141 |
-
default=500,
|
| 142 |
-
help="Maximum tokens in response"
|
| 143 |
-
)
|
| 144 |
-
|
| 145 |
-
args = parser.parse_args()
|
| 146 |
-
|
| 147 |
-
# Setup paths
|
| 148 |
-
workspace_root = Path(__file__).parent.parent
|
| 149 |
-
extracted_dir = workspace_root / args.extracted_dir
|
| 150 |
-
|
| 151 |
-
if not extracted_dir.exists():
|
| 152 |
-
print(f"β Directory not found: {extracted_dir}")
|
| 153 |
-
print("Run extract_priips.py first to extract documents.")
|
| 154 |
-
sys.exit(1)
|
| 155 |
-
|
| 156 |
-
# Load documents
|
| 157 |
-
print(f"π Loading documents from {extracted_dir}...")
|
| 158 |
-
documents = load_extracted_documents(extracted_dir)
|
| 159 |
-
|
| 160 |
-
if not documents:
|
| 161 |
-
print("β οΈ No extracted documents found.")
|
| 162 |
-
print("Add PDFs to priips_documents/raw/ and run extract_priips.py")
|
| 163 |
-
sys.exit(1)
|
| 164 |
-
|
| 165 |
-
print(f"β
Loaded {len(documents)} documents")
|
| 166 |
-
|
| 167 |
-
# Build context
|
| 168 |
-
context = build_context(documents, args.query, args.max_context)
|
| 169 |
-
|
| 170 |
-
# Query LLM
|
| 171 |
-
print(f"\nβ Question: {args.query}\n")
|
| 172 |
-
answer = query_llm(args.query, context, args.max_tokens)
|
| 173 |
-
|
| 174 |
-
print(f"\n㪠Answer:\n{answer}\n")
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
if __name__ == "__main__":
|
| 178 |
-
main()
|
| 179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scripts/test_model_access.py
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script to verify access to DragonLLM models using Hugging Face Hub.
|
| 4 |
+
|
| 5 |
+
This script tests:
|
| 6 |
+
1. Token detection and authentication
|
| 7 |
+
2. Model repository access
|
| 8 |
+
3. Model information retrieval
|
| 9 |
+
4. Token permissions
|
| 10 |
+
|
| 11 |
+
Note: You can also use the HF MCP server if available:
|
| 12 |
+
- Uses huggingface_hub library directly
|
| 13 |
+
- Compatible with MCP server setup
|
| 14 |
+
|
| 15 |
+
Run with: python scripts/test_model_access.py
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import os
|
| 19 |
+
import sys
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
|
| 22 |
+
# Add parent directory to path for imports
|
| 23 |
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
| 24 |
+
|
| 25 |
+
try:
|
| 26 |
+
from huggingface_hub import login, whoami, HfApi, model_info, get_token
|
| 27 |
+
from huggingface_hub.utils import HfHubHTTPError
|
| 28 |
+
except ImportError:
|
| 29 |
+
print("β Error: huggingface_hub not installed")
|
| 30 |
+
print(" Install it with: pip install huggingface-hub")
|
| 31 |
+
sys.exit(1)
|
| 32 |
+
|
| 33 |
+
# Model to test access to
|
| 34 |
+
MODEL_NAME = "DragonLLM/qwen3-8b-fin-v1.0"
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def get_hf_token():
|
| 38 |
+
"""Get Hugging Face token from environment variables or HF CLI cache"""
|
| 39 |
+
# First try environment variables (priority for HF Spaces)
|
| 40 |
+
token = (
|
| 41 |
+
os.getenv("HF_TOKEN_LC2") or
|
| 42 |
+
os.getenv("HF_TOKEN_LC") or
|
| 43 |
+
os.getenv("HF_TOKEN") or
|
| 44 |
+
os.getenv("HUGGING_FACE_HUB_TOKEN")
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
if token:
|
| 48 |
+
# Determine source
|
| 49 |
+
if os.getenv("HF_TOKEN_LC2"):
|
| 50 |
+
source = "HF_TOKEN_LC2 (env)"
|
| 51 |
+
elif os.getenv("HF_TOKEN_LC"):
|
| 52 |
+
source = "HF_TOKEN_LC (env)"
|
| 53 |
+
elif os.getenv("HF_TOKEN"):
|
| 54 |
+
source = "HF_TOKEN (env)"
|
| 55 |
+
else:
|
| 56 |
+
source = "HUGGING_FACE_HUB_TOKEN (env)"
|
| 57 |
+
return token, source
|
| 58 |
+
|
| 59 |
+
# Fall back to HF CLI cached token (if available)
|
| 60 |
+
try:
|
| 61 |
+
cached_token = get_token()
|
| 62 |
+
if cached_token:
|
| 63 |
+
return cached_token, "HF CLI cache"
|
| 64 |
+
except Exception:
|
| 65 |
+
pass
|
| 66 |
+
|
| 67 |
+
return None, None
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def test_token_detection():
|
| 71 |
+
"""Test 1: Check if token is found in environment"""
|
| 72 |
+
print("\n" + "="*70)
|
| 73 |
+
print("TEST 1: Token Detection")
|
| 74 |
+
print("="*70)
|
| 75 |
+
|
| 76 |
+
token, source = get_hf_token()
|
| 77 |
+
|
| 78 |
+
if token:
|
| 79 |
+
print(f"β
Token found: {source}")
|
| 80 |
+
print(f" Token length: {len(token)} characters")
|
| 81 |
+
print(f" Token preview: {token[:10]}...{token[-4:]}")
|
| 82 |
+
return True, token, source
|
| 83 |
+
else:
|
| 84 |
+
print("β No token found in environment!")
|
| 85 |
+
print("\n Checked environment variables:")
|
| 86 |
+
print(" - HF_TOKEN_LC2 (recommended for DragonLLM)")
|
| 87 |
+
print(" - HF_TOKEN_LC")
|
| 88 |
+
print(" - HF_TOKEN")
|
| 89 |
+
print(" - HUGGING_FACE_HUB_TOKEN")
|
| 90 |
+
print("\n To set a token:")
|
| 91 |
+
print(" export HF_TOKEN_LC2='your_token_here'")
|
| 92 |
+
print(" Or use: huggingface-cli login")
|
| 93 |
+
return False, None, None
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def test_authentication(token):
|
| 97 |
+
"""Test 2: Authenticate with Hugging Face Hub"""
|
| 98 |
+
print("\n" + "="*70)
|
| 99 |
+
print("TEST 2: Hugging Face Hub Authentication")
|
| 100 |
+
print("="*70)
|
| 101 |
+
|
| 102 |
+
try:
|
| 103 |
+
# Login with token
|
| 104 |
+
login(token=token, add_to_git_credential=False)
|
| 105 |
+
print("β
Successfully authenticated with Hugging Face Hub")
|
| 106 |
+
|
| 107 |
+
# Get user info
|
| 108 |
+
try:
|
| 109 |
+
user_info = whoami()
|
| 110 |
+
print(f"β
Logged in as: {user_info.get('name', 'Unknown')}")
|
| 111 |
+
if 'type' in user_info:
|
| 112 |
+
print(f" Account type: {user_info['type']}")
|
| 113 |
+
return True
|
| 114 |
+
except Exception as e:
|
| 115 |
+
print(f"β οΈ Authenticated but couldn't get user info: {e}")
|
| 116 |
+
return True # Still authenticated even if we can't get user info
|
| 117 |
+
|
| 118 |
+
except Exception as e:
|
| 119 |
+
print(f"β Authentication failed: {e}")
|
| 120 |
+
print("\n Possible causes:")
|
| 121 |
+
print(" 1. Invalid token")
|
| 122 |
+
print(" 2. Token expired")
|
| 123 |
+
print(" 3. Network connectivity issues")
|
| 124 |
+
return False
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def test_model_access(model_name):
|
| 128 |
+
"""Test 3: Check if we can access the model repository"""
|
| 129 |
+
print("\n" + "="*70)
|
| 130 |
+
print("TEST 3: Model Repository Access")
|
| 131 |
+
print("="*70)
|
| 132 |
+
print(f"Model: {model_name}")
|
| 133 |
+
|
| 134 |
+
try:
|
| 135 |
+
# Try to get model info
|
| 136 |
+
print(f" Attempting to access model repository...")
|
| 137 |
+
info = model_info(model_name, token=True)
|
| 138 |
+
|
| 139 |
+
print(f"β
Successfully accessed model repository!")
|
| 140 |
+
print(f" Model ID: {info.id}")
|
| 141 |
+
print(f" Model tags: {', '.join(info.tags) if info.tags else 'None'}")
|
| 142 |
+
|
| 143 |
+
# Check if model is gated
|
| 144 |
+
if hasattr(info, 'gated') and info.gated:
|
| 145 |
+
print(f" β οΈ Model is GATED - requires accepting terms")
|
| 146 |
+
|
| 147 |
+
# Check available files
|
| 148 |
+
if hasattr(info, 'siblings'):
|
| 149 |
+
file_count = len(info.siblings) if info.siblings else 0
|
| 150 |
+
print(f" Files in repository: {file_count}")
|
| 151 |
+
if file_count > 0 and info.siblings:
|
| 152 |
+
print(f" Sample files:")
|
| 153 |
+
for sibling in info.siblings[:5]:
|
| 154 |
+
print(f" - {sibling.rfilename} ({sibling.size / (1024**2):.1f} MB)")
|
| 155 |
+
if file_count > 5:
|
| 156 |
+
print(f" ... and {file_count - 5} more files")
|
| 157 |
+
|
| 158 |
+
return True
|
| 159 |
+
|
| 160 |
+
except HfHubHTTPError as e:
|
| 161 |
+
if e.response.status_code == 401:
|
| 162 |
+
print(f"β Unauthorized (401): Token doesn't have access to this model")
|
| 163 |
+
print("\n Possible causes:")
|
| 164 |
+
print(" 1. You haven't accepted the model's terms of use")
|
| 165 |
+
print(f" 2. Visit: https://huggingface.co/{model_name}")
|
| 166 |
+
print(" 3. Click 'Agree and access repository'")
|
| 167 |
+
print(" 4. Token doesn't have proper permissions")
|
| 168 |
+
return False
|
| 169 |
+
elif e.response.status_code == 403:
|
| 170 |
+
print(f"β Forbidden (403): Access denied to this model")
|
| 171 |
+
print("\n This model may be private or require special access")
|
| 172 |
+
return False
|
| 173 |
+
elif e.response.status_code == 404:
|
| 174 |
+
print(f"β Not Found (404): Model doesn't exist")
|
| 175 |
+
return False
|
| 176 |
+
else:
|
| 177 |
+
print(f"β HTTP Error {e.response.status_code}: {e}")
|
| 178 |
+
return False
|
| 179 |
+
except Exception as e:
|
| 180 |
+
print(f"β Error accessing model: {e}")
|
| 181 |
+
print(f" Error type: {type(e).__name__}")
|
| 182 |
+
return False
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def test_model_files(model_name):
|
| 186 |
+
"""Test 4: Check if we can list model files"""
|
| 187 |
+
print("\n" + "="*70)
|
| 188 |
+
print("TEST 4: Model Files Access")
|
| 189 |
+
print("="*70)
|
| 190 |
+
|
| 191 |
+
try:
|
| 192 |
+
api = HfApi()
|
| 193 |
+
files = api.list_repo_files(
|
| 194 |
+
repo_id=model_name,
|
| 195 |
+
repo_type="model",
|
| 196 |
+
token=True
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
if files:
|
| 200 |
+
print(f"β
Found {len(files)} files in model repository")
|
| 201 |
+
print(f" Key files:")
|
| 202 |
+
|
| 203 |
+
# Show important files
|
| 204 |
+
important_files = [
|
| 205 |
+
f for f in files if any(
|
| 206 |
+
ext in f.lower()
|
| 207 |
+
for ext in ['.safetensors', '.bin', 'config.json', 'tokenizer', 'model']
|
| 208 |
+
)
|
| 209 |
+
]
|
| 210 |
+
|
| 211 |
+
for file in important_files[:10]:
|
| 212 |
+
print(f" - {file}")
|
| 213 |
+
if len(files) > 10:
|
| 214 |
+
print(f" ... and {len(files) - 10} more files")
|
| 215 |
+
|
| 216 |
+
return True
|
| 217 |
+
else:
|
| 218 |
+
print("β οΈ No files found in repository")
|
| 219 |
+
return False
|
| 220 |
+
|
| 221 |
+
except Exception as e:
|
| 222 |
+
print(f"β Error listing files: {e}")
|
| 223 |
+
return False
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def test_token_permissions(token):
|
| 227 |
+
"""Test 5: Check token permissions"""
|
| 228 |
+
print("\n" + "="*70)
|
| 229 |
+
print("TEST 5: Token Permissions")
|
| 230 |
+
print("="*70)
|
| 231 |
+
|
| 232 |
+
try:
|
| 233 |
+
api = HfApi()
|
| 234 |
+
user_info = api.whoami(token=token)
|
| 235 |
+
|
| 236 |
+
print(f"β
Token has valid permissions")
|
| 237 |
+
print(f" User: {user_info.get('name', 'Unknown')}")
|
| 238 |
+
print(f" Type: {user_info.get('type', 'Unknown')}")
|
| 239 |
+
|
| 240 |
+
# Check if user has read access
|
| 241 |
+
if 'canRead' in user_info:
|
| 242 |
+
print(f" Can read repositories: {user_info['canRead']}")
|
| 243 |
+
|
| 244 |
+
return True
|
| 245 |
+
|
| 246 |
+
except Exception as e:
|
| 247 |
+
print(f"β Error checking permissions: {e}")
|
| 248 |
+
return False
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def main():
|
| 252 |
+
"""Run all tests"""
|
| 253 |
+
print("\n" + "#"*70)
|
| 254 |
+
print("# DragonLLM Model Access Test")
|
| 255 |
+
print("#"*70)
|
| 256 |
+
print(f"Testing access to: {MODEL_NAME}")
|
| 257 |
+
|
| 258 |
+
results = {}
|
| 259 |
+
|
| 260 |
+
# Test 1: Token detection
|
| 261 |
+
success, token, source = test_token_detection()
|
| 262 |
+
results['token_detection'] = success
|
| 263 |
+
|
| 264 |
+
if not success:
|
| 265 |
+
print("\n" + "="*70)
|
| 266 |
+
print("β Cannot proceed without a token")
|
| 267 |
+
print("="*70)
|
| 268 |
+
return
|
| 269 |
+
|
| 270 |
+
# Test 2: Authentication
|
| 271 |
+
results['authentication'] = test_authentication(token)
|
| 272 |
+
|
| 273 |
+
if not results['authentication']:
|
| 274 |
+
print("\n" + "="*70)
|
| 275 |
+
print("β Authentication failed - cannot proceed")
|
| 276 |
+
print("="*70)
|
| 277 |
+
return
|
| 278 |
+
|
| 279 |
+
# Test 3: Model access
|
| 280 |
+
results['model_access'] = test_model_access(MODEL_NAME)
|
| 281 |
+
|
| 282 |
+
# Test 4: Model files (only if model access succeeded)
|
| 283 |
+
if results['model_access']:
|
| 284 |
+
results['model_files'] = test_model_files(MODEL_NAME)
|
| 285 |
+
else:
|
| 286 |
+
results['model_files'] = False
|
| 287 |
+
|
| 288 |
+
# Test 5: Token permissions
|
| 289 |
+
results['token_permissions'] = test_token_permissions(token)
|
| 290 |
+
|
| 291 |
+
# Summary
|
| 292 |
+
print("\n" + "="*70)
|
| 293 |
+
print("SUMMARY")
|
| 294 |
+
print("="*70)
|
| 295 |
+
|
| 296 |
+
for test_name, success in results.items():
|
| 297 |
+
status = "β
PASS" if success else "β FAIL"
|
| 298 |
+
test_display = test_name.replace('_', ' ').title()
|
| 299 |
+
print(f"{status} - {test_display}")
|
| 300 |
+
|
| 301 |
+
passed = sum(1 for v in results.values() if v)
|
| 302 |
+
total = len(results)
|
| 303 |
+
|
| 304 |
+
print(f"\nResults: {passed}/{total} tests passed")
|
| 305 |
+
|
| 306 |
+
if passed == total:
|
| 307 |
+
print("\nπ All tests passed! You have full access to the DragonLLM model.")
|
| 308 |
+
print(" The model can be loaded in your application.")
|
| 309 |
+
elif results.get('token_detection') and results.get('authentication'):
|
| 310 |
+
print("\nβ οΈ Authentication works but model access failed.")
|
| 311 |
+
print(" This usually means:")
|
| 312 |
+
print(" 1. You need to accept the model's terms of use")
|
| 313 |
+
print(f" 2. Visit: https://huggingface.co/{MODEL_NAME}")
|
| 314 |
+
print(" 3. Click 'Agree and access repository'")
|
| 315 |
+
else:
|
| 316 |
+
print("\nβ Some tests failed. Check the errors above for details.")
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
if __name__ == "__main__":
|
| 320 |
+
main()
|
| 321 |
+
|
tests/performance/README.md
CHANGED
|
@@ -269,3 +269,9 @@ For issues or questions:
|
|
| 269 |
- Review DEPLOYMENT.md for configuration details
|
| 270 |
- Verify vLLM is properly initialized with model
|
| 271 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
- Review DEPLOYMENT.md for configuration details
|
| 270 |
- Verify vLLM is properly initialized with model
|
| 271 |
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
|
tests/performance/__init__.py
CHANGED
|
@@ -1,2 +1,8 @@
|
|
| 1 |
# Performance test suite
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# Performance test suite
|
| 2 |
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
|
tests/performance/benchmark.py
CHANGED
|
@@ -342,3 +342,9 @@ async def main():
|
|
| 342 |
if __name__ == "__main__":
|
| 343 |
asyncio.run(main())
|
| 344 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
if __name__ == "__main__":
|
| 343 |
asyncio.run(main())
|
| 344 |
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
|
tests/performance/test_inference_speed.py
CHANGED
|
@@ -240,3 +240,9 @@ async def test_temperature_variance(client):
|
|
| 240 |
if __name__ == "__main__":
|
| 241 |
pytest.main([__file__, "-v", "-s"])
|
| 242 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
if __name__ == "__main__":
|
| 241 |
pytest.main([__file__, "-v", "-s"])
|
| 242 |
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
|
tests/performance/test_openai_compatibility.py
CHANGED
|
@@ -343,3 +343,9 @@ class TestResponseFormat:
|
|
| 343 |
if __name__ == "__main__":
|
| 344 |
pytest.main([__file__, "-v", "-s"])
|
| 345 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
if __name__ == "__main__":
|
| 344 |
pytest.main([__file__, "-v", "-s"])
|
| 345 |
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
|