RunPod serverless worker untuk konversi image → SVG menggunakan OmniSVG (Qwen2.5-VL backbone) dengan dukungan GPU.
Pipeline ini hasil migrasi dari worker Real-ESRGAN upscaler — semua mekanisme storage / webhook / database tetap sama, hanya stage inti upscale yang diganti menjadi image-to-svg generation.
.svg file)temperature, top_p, top_k, repetition_penaltyimage: "warming-up")| Model size | GPU memory | Recommended GPU |
|---|---|---|
| OmniSVG-3B | ~17 GB | RTX 3090 / A5000 |
| OmniSVG1.1_4B | ~17 GB | RTX 3090 / A5000 |
| OmniSVG1.1_8B | ~26 GB | A6000 / A100 40GB |
runpod
transformers==4.51.3
qwen-vl-utils==0.0.11
huggingface-hub
accelerate
einops==0.4.1
PyYAML==6.0.2
Pillow
numpy<2.0.0
CairoSVG==2.7.1
shapely==2.0.7
boto3
requests
sqlalchemy
psycopg2-binary
runpod-worker-image-svg/
├── main.py # RunPod handler & pipeline flow
├── svg_generator.py # OmniSVG image-to-SVG inference wrapper
├── db/ # Database module
│ ├── __init__.py
│ ├── database.py
│ ├── models.py # GeneratedSvg ORM
│ ├── service.py # save_generated_svg()
│ └── migrations/
│ └── init.sql
├── Dockerfile # Container definition (clones OmniSVG repo)
├── docker-compose.yml # Local development setup
├── requirements.txt
└── README.md
┌─────────────────────────────────────────────────────────────┐
│ RunPod Job Input │
│ { image, model_size, num_candidates, temperature, ... } │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 1. Validate Input │
│ Check image, model_size, num_candidates, etc. │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. Load Image (S3 or Network Volume) │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. Generate SVG (GPU, OmniSVG) │
│ ImageToSVG.generate(image, num_candidates, ...) │
│ • Preprocess image (background replace, resize → 448) │
│ • Qwen-VL processor → token inputs │
│ • SketchDecoder.transformer.generate(...) │
│ • SVGTokenizer.process_generated_tokens(...) → SVG string │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. Save Result (Cloudflare or Network Volume) │
│ • Cloudflare: upload .svg → CDN URL │
│ • Volume: write .svg file under OUTPUT_VOLUME_PATH │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 5. Delete Input (Optional) │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 6. Save Record to Database (Optional) │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 7. Send Webhook Callback (Async) │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Return Response │
│ { job_id, output_url, svg, path_count, ... } │
└─────────────────────────────────────────────────────────────┘
docker compose build
# atau
docker build -t your-username/runpod-omnisvg:latest .
Layout volume default (OMNISVG_HOME=/runpod-volume/omnisvg-models):
/runpod-volume/omnisvg-models/
├── OmniSVG1.1_4B/ # berisi pytorch_model.bin
│ └── pytorch_model.bin
└── qwen-4B/ # Qwen2.5-VL-3B-Instruct snapshot
├── config.json
├── tokenizer.json
└── ...
Download dari HuggingFace:
pip install huggingface-hub
huggingface-cli download OmniSVG/OmniSVG1.1_4B --local-dir /runpod-volume/omnisvg-models/OmniSVG1.1_4B
huggingface-cli download Qwen/Qwen2.5-VL-3B-Instruct --local-dir /runpod-volume/omnisvg-models/qwen-4B
# Untuk model 8B:
huggingface-cli download OmniSVG/OmniSVG1.1_8B --local-dir /runpod-volume/omnisvg-models/OmniSVG1.1_8B
huggingface-cli download Qwen/Qwen2.5-VL-7B-Instruct --local-dir /runpod-volume/omnisvg-models/qwen-8B
Alternatif: kosongkan
OMNISVG_MODEL_PATH/QWEN_MODEL_PATHdan worker akan auto-download dari HuggingFace pada cold-start pertama (lebih lambat).
/runpod-volume.| Variable | Default | Keterangan |
|---|---|---|
INPUT_STORAGE_MODE | s3 | s3 atau volume |
INPUT_VOLUME_PATH | /runpod-volume/inputs/ | Root path untuk input volume |
OUTPUT_STORAGE_MODE | cloudflare | cloudflare atau volume |
OUTPUT_VOLUME_PATH | /runpod-volume/outputs/ | Root path untuk SVG output di volume |
DELETE_INPUT_AFTER_GENERATE | false | Hapus input setelah selesai |
| Variable | Keterangan |
|---|---|
S3_BUCKET | Bucket name |
S3_REGION | Default us-east-1 |
S3_ENDPOINT_URL | Optional, untuk S3-compatible storage |
AWS_ACCESS_KEY_ID | Credentials |
AWS_SECRET_ACCESS_KEY | Credentials |
| Variable | Keterangan |
|---|---|
CLOUDFLARE_ACCOUNT_ID | Cloudflare account ID |
CLOUDFLARE_API_TOKEN | API token dengan permission Images:Edit |
| Variable | Default | Keterangan |
|---|---|---|
OMNISVG_MODEL_SIZE | 4B | 3B, 4B, atau 8B |
OMNISVG_HOME | /runpod-volume/omnisvg-models | Root folder untuk model weights |
OMNISVG_MODEL_PATH | (auto) | Path lokal / HF repo id ke OmniSVG weights folder |
QWEN_MODEL_PATH | (auto) | Path lokal / HF repo id ke Qwen2.5-VL backbone |
OMNISVG_REPO_PATH | /opt/OmniSVG | Path ke clone OmniSVG (sudah di-set di Dockerfile) |
OMNISVG_NUM_CANDIDATES | 1 | Default jumlah kandidat per request |
OMNISVG_MAX_LENGTH | 1024 | Default max token length per request |
OMNISVG_TEMPERATURE | 0.3 | Default sampling temperature |
OMNISVG_TOP_P | 0.90 | Default top-p |
OMNISVG_TOP_K | 50 | Default top-k |
OMNISVG_REP_PENALTY | 1.05 | Default repetition penalty |
| Variable | Keterangan |
|---|---|
ENABLE_DATABASE | true/false |
DATABASE_URL | postgresql+psycopg2://... |
WEBHOOK_CALLBACK_URL | URL endpoint untuk async callback |
WEBHOOK_TIMEOUT_SECONDS | Default 10 |
WEBHOOK_AUTH_TOKEN | Optional bearer token untuk webhook |
{
"input": {
"image": "path/or/key/to/input.png",
"model_size": "4B",
"num_candidates": 1,
"max_length": 1024,
"temperature": 0.3,
"top_p": 0.9,
"top_k": 50,
"repetition_penalty": 1.05,
"replace_background": true,
"return_svg": true,
"webhook_url": "https://your-app.com/webhook/optional",
"webhook_enabled": true
}
}
| Field | Type | Default | Keterangan |
|---|---|---|---|
image | string | required | S3 key atau path relatif di volume |
model_size | string | env default | 3B / 4B / 8B |
num_candidates | int | 1 | 1-8 |
max_length | int | 1024 | Maks token output |
temperature | float | 0.3 | |
top_p | float | 0.9 | |
top_k | int | 50 | |
repetition_penalty | float | 1.05 | |
replace_background | bool | true | Ganti background non-putih → putih sebelum di-feed ke model |
return_svg | bool | true | Sertakan SVG string di response payload |
webhook_url | string | env default | Override webhook per-request |
webhook_enabled | bool | true | Disable webhook per-request |
Kirim {"input": {"image": "warming-up"}} untuk warm-up worker tanpa load model.
{
"job_id": "abc-123",
"status": "COMPLETED",
"processing_time": 12.34,
"input_storage_mode": "s3",
"output_storage_mode": "cloudflare",
"output_url": "https://imagedelivery.net/.../public",
"output_volume": null,
"format": "svg",
"model_size": "4B",
"num_candidates": 1,
"path_count": 17,
"original_size": [1024, 1024],
"target_size": [448, 448],
"svg_bytes": 4321,
"svg": "<svg ...>...</svg>",
"webhook_triggered_at": "2026-05-18T08:00:00+00:00",
"error_message": null
}
Lihat db/migrations/init.sql — tabel runpod_worker_generated_svgs.
448x448 (sesuai image.target_size di
config.yaml). Lebar/tinggi ini di-embed di tag <svg> agar dapat di-scale
via CSS / viewBox di sisi client.OUTPUT_STORAGE_MODE=volume dan baca file dari
OUTPUT_VOLUME_PATH.image: "warming-up".3 commits
Python
98.0%
kvlang
1.6%
RunPod serverless worker untuk konversi image → SVG menggunakan OmniSVG (Qwen2.5-VL backbone) dengan dukungan GPU.
Pipeline ini hasil migrasi dari worker Real-ESRGAN upscaler — semua mekanisme storage / webhook / database tetap sama, hanya stage inti upscale yang diganti menjadi image-to-svg generation.
.svg file)temperature, top_p, top_k, repetition_penaltyimage: "warming-up")| Model size | GPU memory | Recommended GPU |
|---|---|---|
| OmniSVG-3B | ~17 GB | RTX 3090 / A5000 |
| OmniSVG1.1_4B | ~17 GB | RTX 3090 / A5000 |
| OmniSVG1.1_8B | ~26 GB | A6000 / A100 40GB |
runpod
transformers==4.51.3
qwen-vl-utils==0.0.11
huggingface-hub
accelerate
einops==0.4.1
PyYAML==6.0.2
Pillow
numpy<2.0.0
CairoSVG==2.7.1
shapely==2.0.7
boto3
requests
sqlalchemy
psycopg2-binary
runpod-worker-image-svg/
├── main.py # RunPod handler & pipeline flow
├── svg_generator.py # OmniSVG image-to-SVG inference wrapper
├── db/ # Database module
│ ├── __init__.py
│ ├── database.py
│ ├── models.py # GeneratedSvg ORM
│ ├── service.py # save_generated_svg()
│ └── migrations/
│ └── init.sql
├── Dockerfile # Container definition (clones OmniSVG repo)
├── docker-compose.yml # Local development setup
├── requirements.txt
└── README.md
┌─────────────────────────────────────────────────────────────┐
│ RunPod Job Input │
│ { image, model_size, num_candidates, temperature, ... } │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 1. Validate Input │
│ Check image, model_size, num_candidates, etc. │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. Load Image (S3 or Network Volume) │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. Generate SVG (GPU, OmniSVG) │
│ ImageToSVG.generate(image, num_candidates, ...) │
│ • Preprocess image (background replace, resize → 448) │
│ • Qwen-VL processor → token inputs │
│ • SketchDecoder.transformer.generate(...) │
│ • SVGTokenizer.process_generated_tokens(...) → SVG string │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. Save Result (Cloudflare or Network Volume) │
│ • Cloudflare: upload .svg → CDN URL │
│ • Volume: write .svg file under OUTPUT_VOLUME_PATH │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 5. Delete Input (Optional) │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 6. Save Record to Database (Optional) │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 7. Send Webhook Callback (Async) │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Return Response │
│ { job_id, output_url, svg, path_count, ... } │
└─────────────────────────────────────────────────────────────┘
docker compose build
# atau
docker build -t your-username/runpod-omnisvg:latest .
Layout volume default (OMNISVG_HOME=/runpod-volume/omnisvg-models):
/runpod-volume/omnisvg-models/
├── OmniSVG1.1_4B/ # berisi pytorch_model.bin
│ └── pytorch_model.bin
└── qwen-4B/ # Qwen2.5-VL-3B-Instruct snapshot
├── config.json
├── tokenizer.json
└── ...
Download dari HuggingFace:
pip install huggingface-hub
huggingface-cli download OmniSVG/OmniSVG1.1_4B --local-dir /runpod-volume/omnisvg-models/OmniSVG1.1_4B
huggingface-cli download Qwen/Qwen2.5-VL-3B-Instruct --local-dir /runpod-volume/omnisvg-models/qwen-4B
# Untuk model 8B:
huggingface-cli download OmniSVG/OmniSVG1.1_8B --local-dir /runpod-volume/omnisvg-models/OmniSVG1.1_8B
huggingface-cli download Qwen/Qwen2.5-VL-7B-Instruct --local-dir /runpod-volume/omnisvg-models/qwen-8B
Alternatif: kosongkan
OMNISVG_MODEL_PATH/QWEN_MODEL_PATHdan worker akan auto-download dari HuggingFace pada cold-start pertama (lebih lambat).
/runpod-volume.| Variable | Default | Keterangan |
|---|---|---|
INPUT_STORAGE_MODE | s3 | s3 atau volume |
INPUT_VOLUME_PATH | /runpod-volume/inputs/ | Root path untuk input volume |
OUTPUT_STORAGE_MODE | cloudflare | cloudflare atau volume |
OUTPUT_VOLUME_PATH | /runpod-volume/outputs/ | Root path untuk SVG output di volume |
DELETE_INPUT_AFTER_GENERATE | false | Hapus input setelah selesai |
| Variable | Keterangan |
|---|---|
S3_BUCKET | Bucket name |
S3_REGION | Default us-east-1 |
S3_ENDPOINT_URL | Optional, untuk S3-compatible storage |
AWS_ACCESS_KEY_ID | Credentials |
AWS_SECRET_ACCESS_KEY | Credentials |
| Variable | Keterangan |
|---|---|
CLOUDFLARE_ACCOUNT_ID | Cloudflare account ID |
CLOUDFLARE_API_TOKEN | API token dengan permission Images:Edit |
| Variable | Default | Keterangan |
|---|---|---|
OMNISVG_MODEL_SIZE | 4B | 3B, 4B, atau 8B |
OMNISVG_HOME | /runpod-volume/omnisvg-models | Root folder untuk model weights |
OMNISVG_MODEL_PATH | (auto) | Path lokal / HF repo id ke OmniSVG weights folder |
QWEN_MODEL_PATH | (auto) | Path lokal / HF repo id ke Qwen2.5-VL backbone |
OMNISVG_REPO_PATH | /opt/OmniSVG | Path ke clone OmniSVG (sudah di-set di Dockerfile) |
OMNISVG_NUM_CANDIDATES | 1 | Default jumlah kandidat per request |
OMNISVG_MAX_LENGTH | 1024 | Default max token length per request |
OMNISVG_TEMPERATURE | 0.3 | Default sampling temperature |
OMNISVG_TOP_P | 0.90 | Default top-p |
OMNISVG_TOP_K | 50 | Default top-k |
OMNISVG_REP_PENALTY | 1.05 | Default repetition penalty |
| Variable | Keterangan |
|---|---|
ENABLE_DATABASE | true/false |
DATABASE_URL | postgresql+psycopg2://... |
WEBHOOK_CALLBACK_URL | URL endpoint untuk async callback |
WEBHOOK_TIMEOUT_SECONDS | Default 10 |
WEBHOOK_AUTH_TOKEN | Optional bearer token untuk webhook |
{
"input": {
"image": "path/or/key/to/input.png",
"model_size": "4B",
"num_candidates": 1,
"max_length": 1024,
"temperature": 0.3,
"top_p": 0.9,
"top_k": 50,
"repetition_penalty": 1.05,
"replace_background": true,
"return_svg": true,
"webhook_url": "https://your-app.com/webhook/optional",
"webhook_enabled": true
}
}
| Field | Type | Default | Keterangan |
|---|---|---|---|
image | string | required | S3 key atau path relatif di volume |
model_size | string | env default | 3B / 4B / 8B |
num_candidates | int | 1 | 1-8 |
max_length | int | 1024 | Maks token output |
temperature | float | 0.3 | |
top_p | float | 0.9 | |
top_k | int | 50 | |
repetition_penalty | float | 1.05 | |
replace_background | bool | true | Ganti background non-putih → putih sebelum di-feed ke model |
return_svg | bool | true | Sertakan SVG string di response payload |
webhook_url | string | env default | Override webhook per-request |
webhook_enabled | bool | true | Disable webhook per-request |
Kirim {"input": {"image": "warming-up"}} untuk warm-up worker tanpa load model.
{
"job_id": "abc-123",
"status": "COMPLETED",
"processing_time": 12.34,
"input_storage_mode": "s3",
"output_storage_mode": "cloudflare",
"output_url": "https://imagedelivery.net/.../public",
"output_volume": null,
"format": "svg",
"model_size": "4B",
"num_candidates": 1,
"path_count": 17,
"original_size": [1024, 1024],
"target_size": [448, 448],
"svg_bytes": 4321,
"svg": "<svg ...>...</svg>",
"webhook_triggered_at": "2026-05-18T08:00:00+00:00",
"error_message": null
}
Lihat db/migrations/init.sql — tabel runpod_worker_generated_svgs.
448x448 (sesuai image.target_size di
config.yaml). Lebar/tinggi ini di-embed di tag <svg> agar dapat di-scale
via CSS / viewBox di sisi client.OUTPUT_STORAGE_MODE=volume dan baca file dari
OUTPUT_VOLUME_PATH.image: "warming-up".3 commits
Python
98.0%
kvlang
1.6%