← Back to list

Fine-Tuning Qwen3-Embedding with SWIFT and Docker: A Complete, Practical Guide

A practical end-to-end guide to fine-tuning Qwen3-Embedding using ModelScope SWIFT

Doil Kim · 2025-11-25 13:32 · 0 claps · 5.9 min read paywalled
#qwen-3 #fine-tuning #swift #lora #deepspeed
Open on Medium ↗
Wiki topics: LLM · Large Language Models RAG · RAG & Retrieval FT · Fine-tuning & Adaptation 📱 · Mobile Development ☁️ · DevOps & Cloud

Fine-Tuning Qwen3-Embedding with SWIFT and Docker: A Complete, Practical Guide

Image by author — Generated with ChatGPT

Image by author — Generated with ChatGPT

This story was written with the assistance of an AI writing program.

Large-scale retrieval systems often require more than generic, off-the-shelf embedding models. When accuracy, domain specificity, or search precision becomes critical, the solution is often straightforward: fine-tune your embedding model on your own data.

The Qwen3-Embedding family supports further training, and the **ModelScope SWIFT** framework makes this process both scalable and production-ready.

This guide provides a full end-to-end pipeline — from Docker environment setup, data preparation, InfoNCE configuration, LoRA vs Full FT options, multi-GPU training, and practical troubleshooting for OOM issues — all tailored for Qwen3-Embedding.

1. Why SWIFT for Qwen3-Embedding

ModelScope SWIFT is a large-model training and deployment framework built for real-world tasks:

Key Features

  • Model Variety: 500+ text models, 200+ multimodal models
  • Hardware Support: CPU, RTX GPUs, T4/V100/A10/A100/H100, Ascend, MPS
  • Training Methods: Full fine-tuning, LoRA, QLoRA, DoRA, etc.
  • Distributed Training: DDP, FSDP, DeepSpeed ZeRO-2/3, Megatron tensor/pipeline/sequence parallelism
  • RLHF Training: DPO, PPO, GRPO, KTO, DAPO, reward models, etc.

For embedding models like Qwen3-Embedding, SWIFT is ideal because it supports InfoNCE loss, multi-GPU scaling, and parameter-efficient tuning.

Why SWIFT for embedding models?

For embedding models like Qwen3-Embedding, SWIFT is an ideal choice because it:

  • provides native support for InfoNCE loss, the same loss used in the original Qwen3-Embedding paper,
  • scales seamlessly across multiple GPUs,
  • supports both LoRA and full-parameter fine-tuning,
  • and integrats cleanly with PyTorch + DeepSpeed.

Most importantly, 👉 this training pipeline follows the official guidance from the Qwen3-Embedding GitHub repository, specifically:

This ensures the setup aligns with how Qwen developers themselves recommend training the model.

2. Environment Setup

Before starting any fine-tuning work, we assume that you are setting up a dedicated development environment — either on a local workstation with GPUs, a remote server, or inside a Docker container. The following Python packages form the core runtime environment for training Qwen3-Embedding using SWIFT.

pip install ms-swift -U
# Or install from source:
pip install git+https://github.com/modelscope/ms-swift.git

pip install transformers -U
pip install deepspeed
pip install liger-kernel
pip install flash-attn --no-build-isolation

3. Docker Image for SWIFT + Qwen3-Embedding

Training large embedding models — especially with LoRA, DeepSpeed, FlashAttention, or multi-GPU configurations — requires a clean, reproducible, and GPU-optimized runtime environment. To avoid dependency conflicts or version mismatches, it is best practice to encapsulate the entire stack inside a Docker image.

The following Dockerfile builds a fully functional environment for SWIFT-based fine-tuning of Qwen3-Embedding, including CUDA, DeepSpeed, MPI, FlashAttention, and SWIFT itself.

docker/Dockerfile

FROM pytorch/pytorch:2.7.1-cuda12.8-cudnn9-devel

SHELL ["/bin/bash", "-c"]

RUN apt-get update && \
    apt-get install -y --no-install-recommends git build-essential && \
    rm -rf /var/lib/apt/lists/*

RUN conda update -n base -c defaults conda -y && \
    conda install -y -c conda-forge openmpi mpi4py && \
    pip install --no-cache-dir -U deepspeed

WORKDIR /opt

RUN git clone https://github.com/modelscope/ms-swift.git

WORKDIR /opt/ms-swift

RUN pip install --no-cache-dir -e '.[all]'
RUN pip install --no-cache-dir ninja packaging wheel && \
    pip install --no-cache-dir --no-build-isolation flash-attn && \
    pip install --no-cache-dir msgspec

WORKDIR /workspace

4. Project Layout

A clean directory structure helps keep the training pipeline reproducible and easy to maintain. Each top-level folder has a clear responsibility:

project/
├── docker/
│   ├── Dockerfile
│   └── docker-compose.yml
├── scripts/
│   ├── train.sh
│   └── config/
│       └── swift.yaml
├── data/        (mounted)
├── models/      (mounted)
└── output/      (mounted)

docker/

Contains everything related to the runtime environment.

  • Dockerfile → builds the GPU-ready SWIFT training image
  • docker-compose.yml → mounts folders, sets GPU IDs, and launches training

scripts/

Holds the training logic and configuration.

  • train.sh → entrypoint script (detects GPUs, launches SWIFT)
  • config/swift.yaml → SWIFT training configuration (model, loss, batch size, etc.)

data/

Mounted read-only at runtime. Contains the training dataset (e.g., train.jsonl).

models/

Mounted read-only. Contains the base Qwen3-Embedding model you want to fine-tune.

output/

Mounted read-write. Stores all checkpoints, LoRA adapters, logs, and evaluation plots generated during training.

5. docker-compose: GPU-Aware

docker/docker-compose.yml

version: "3.8"

services:
  swift-train:
    image: ${IMAGE}
    container_name: swift-train
    ipc: host
    shm_size: "32g"
    restart: "no"
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              device_ids: ["${GPU_IDS}"]
              capabilities: [gpu]
    volumes:
      - ${DATA_DIR}:/workspace/data:ro
      - ${MODEL_DIR}:/workspace/models:ro
      - ${OUTPUT_DIR}:/workspace/output
      - ${SCRIPTS_DIR}:/workspace/scripts
    working_dir: /workspace
    entrypoint: ["/bin/bash", "-lc", "/workspace/scripts/train.sh"]

6. .env Configuration

.env

IMAGE=pytorch-swift-custom:latest

GPU_IDS=0,1
DATA_DIR=/path/to/dataset
MODEL_DIR=/path/to/model
OUTPUT_DIR=/path/to/output
SCRIPTS_DIR=./scripts

7. SWIFT YAML Config (LoRA Fine-Tuning)

scripts/config/swift.yaml

model: /workspace/models
task_type: embedding
model_type: qwen3_emb
train_type: lora

attn_impl: flash_attn
dataset: /workspace/data/train.jsonl
split_dataset_ratio: 0.3
output_dir: /workspace/output

eval_strategy: steps
eval_steps: 200
num_train_epochs: 4
save_steps: 200

per_device_train_batch_size: 8
per_device_eval_batch_size: 8
gradient_accumulation_steps: 1

learning_rate: 1e-4
loss_type: infonce
dataloader_drop_last: true
max_length: 1024
torch_dtype: float16

deepspeed: zero3
save_only_model: true
save_total_limit: 1

To switch from LoRA to Full Fine-Tuning:

train_type: full

⚠️ Full FT significantly increases GPU memory usage.

8. Training Script with GPU Auto-Detection

scripts/train.sh

#!/usr/bin/env bash
set -euo pipefail

echo "[swift] Training job started."
CONFIG_FILE="/workspace/scripts/config/swift.yaml"
if [[ ! -f "$CONFIG_FILE" ]]; then
    echo "[error] Config file not found: $CONFIG_FILE"
    exit 1
fi
if [[ -z "${GPU_IDS:-}" ]]; then
    echo "[warning] GPU_IDS not set. Defaulting NPROC_PER_NODE=1."
    nproc_per_node=1
else
    gpu_ids_clean=$(echo "$GPU_IDS" | tr -d ' ')
    IFS=',' read -ra gpu_array <<< "$gpu_ids_clean"
    nproc_per_node=${#gpu_array[@]}
fi
echo "[swift] Using GPU IDs: $GPU_IDS"
echo "[swift] Detected GPU count: $nproc_per_node"
export NPROC_PER_NODE=$nproc_per_node

NPROC_PER_NODE=$nproc_per_node swift sft --config "$CONFIG_FILE"
echo "[swift] Training completed."

💡 Important: Distributed training requires the correct NPROC_PER_NODE.

9. Data Preparation for InfoNCE

Qwen3-Embedding uses a conversational message format for training samples. Each example contains an anchor (messages), one positive (positive_messages), and optionally hard negatives (negative_messages).

Positive-only sample

{"messages":[{"role":"user","content":"sentence1"}], "positive_messages":[[{"role":"user","content":"sentence2"}]]}

With multiple hard negatives

{"messages":[{"role":"user","content":"sentence1"}], "positive_messages":[[{"role":"user","content":"sentence2"}]], "negative_messages":[[{"role":"user","content":"sentence3"}],[{"role":"user","content":"sentence4"}]]}

Constraints:

  • messages → anchor (single list)
  • positive_messages → list-of-list, outer length must be 1
  • negative_messages optional → used as hard negatives

If omitted, in-batch negatives are used.

Ref: https://swift.readthedocs.io/en/latest/BestPractices/Embedding.html

✅ Only one dataset file is needed

For training, you only need a single JSONL file, typically:

train.jsonl

Every sample (anchor, positive, negative) is stored line-by-line inside this file.

✅ Validation dataset is created automatically

You do not need to prepare a separate validation file.

SWIFT automatically handles validation when you set:

split_dataset_ratio: 0.3

For example:

  • split_dataset_ratio: 0.3 → 70% training, 30% validation
  • SWIFT internally generates the validation dataset from train.jsonl
  • Outputs like val_dataset.jsonl are automatically saved in the checkpoint folder

10. Adding Custom Instructions

Default embedding prompt:

{Query}<|endoftext|>

With system instruction:

{Instruction} {Query}<|endoftext|>

Example:

{"messages":[{"role":"system","content":"Given a web search query, retrieve relevant passages that answer the query"},{"role":"user","content":"Who are you?"}],"positive_messages": [[{"role": "user", "content": "sentence2"}]], "negative_messages": [[{"role": "user", "content": "sentence3"}], [{"role": "user", "content": "sentence4"}]]}

System content is automatically prepended to the first user message.

11. InfoNCE Loss Settings

Qwen3-Embedding is trained with InfoNCE, so continuing with that loss is recommended.

Environment variables:

✔ INFONCE_TEMPERATURE

Default: 0.01

✔ INFONCE_USE_BATCH

Use in-batch negatives (default: True)

✔ INFONCE_HARD_NEGATIVES

Limit number of hard negatives per sample

  • If unset → use all
  • If set → truncate or pad to fixed count

✔ INFONCE_MASK_FAKE_NEGATIVE

Avoid “false negatives” that behave like positives.

12. Output Artifacts for LoRA vs Full Fine-Tuning

LoRA Output Structure

After training, the LoRA fine-tuning outputs include:

output/
└── checkpoint-XXXX/
    ├── adapter_config.json
    ├── adapter_model.safetensors
    ├── trainer_state.json
    ├── logging.jsonl
    ├── val_dataset.jsonl
    └── images/
         ├── eval_loss.png
         ├── train_loss.png
         └── ...

These files represent:

  • adapter_model.safetensors → LoRA weight matrices
  • adapter_config.json → LoRA hyperparameters (rank, alpha, modules)
  • trainer_state.json → global training metadata
  • logging.jsonl → step-by-step metrics
  • PNG files → visualized training/evaluation curves

Full Fine-Tuning Output

If you switch:

train_type: full

SWIFT produces full model weights, not LoRA adapters. However:

  • GPU memory usage increases sharply (8B model → large VRAM requirement)
  • Checkpoints include full updated model parameters

Official CLI parameter documentation: https://swift.readthedocs.io/en/latest/Instruction/Command-line-parameters.html

13. Running the Training

docker compose --env-file ../.env up

14. Practical OOM Troubleshooting

1. Reduce effective batch size

  • Lower per_device_train_batch_size
  • Increase gradient_accumulation_steps if needed

2. Ensure sequence lengths are controlled

  • Sometimes max_length does not truncate inputs
  • Manually pre-truncate long texts

3. Limit number of hard negatives

  • More hard negatives = larger effective batch
  • Control via INFONCE_HARD_NEGATIVES

4. Verify GPU visibility

  • GPU_IDS must match actual GPUs
  • NPROC_PER_NODE must match the GPU count

15. Final Thoughts

Fine-tuning Qwen3-Embedding is one of the most impactful ways to upgrade a retrieval system — especially when working with specialized domains like finance, education, legal documents, or enterprise knowledge systems.

This guide provided:

  • A fully production-ready Docker environment
  • GPU-aware distributed training with SWIFT
  • Data preparation strategies for InfoNCE
  • Clear distinction between LoRA and Full Fine-Tuning
  • Practical OOM guidance from real-world experience

With this setup, you can confidently iterate on model versions, experiment with hard negative strategies, customize embedding behaviors, and ultimately achieve significantly better retrieval results.

If you have questions, feel free to reach out or leave a comment. Don’t forget to hit the like button and subscribe for more content! 😊


메타데이터
post_id
e4107c2781c9
slug
fine-tuning-qwen3-embedding-with-swift-and-docker-a-complete-practical-guide-e4107c2781c9
url
https://medium.com/@kimdoil1211/fine-tuning-qwen3-embedding-with-swift-and-docker-a-complete-practical-guide-e4107c2781c9
canonical_url
https://medium.com/@kimdoil1211/fine-tuning-qwen3-embedding-with-swift-and-docker-a-complete-practical-guide-e4107c2781c9
author_url
https://medium.com/@kimdoil1211
status
ok
fetched_at
2026-07-13 06:23:13