Главная / Статьи / Два рабочих нагрузки LLM на одной GPU: vLLM LoRA, агенты и настройка параметров в Azure ML

Два рабочих нагрузки LLM на одной GPU: vLLM LoRA, агенты и настройка параметров в Azure ML

Обрабатывайте интерактивный и пакетный трафик агентов с одной карты с использованием LoRA для удовлетворения спроса, аналитики ClickHouse, артефактов в формате Blob, а также тренировки Unsloth с отслеживанием в MLflow.

1917 слов

Многим командам нужны как интерактивные чаты с агентами, так и пакетный анализ на основе одной и той же семьи моделей, отточенных под конкретные задачи — но бюджет на GPU позволяет использовать только одну карту. Данная архитектура обеспечивает работу двух типов нагрузок, связанных с большими языковыми моделями, на одном GPU в Azure: путь с низкой задержкой для вывода результатов с использованием адаптеров LoRA, загружаемых по мере необходимости через vLLM, и путь для аналитики с использованием агентов, который записывает структурированные данные в ClickHouse, при этом обучение и отточка моделей происходят в Azure ML с применением Unsloth и MLflow.

vLLM хранит базовую модель. Адаптеры LoRA загружаются по мере необходимости, чтобы специализированные функции (тон общения, извлечение аналитических данных, варианты вызова инструментов) не требовали создания отдельных полных копий моделей. Размеры адаптеров должны быть небольшими; чрезмерное количество адаптеров приводит к росту задержек из-за их постоянной замены.

vllm serve your-org/base-vlm-7b \
  --enable-lora \
  --max-lora-rank 64 \
  --lora-modules domain-adapter=/data/lora-adapters/current/ \
  --host 0.0.0.0 \
  --port 8000 \
  --gpu-memory-utilization 0.90 \
  --max-model-len 8192
payload = {
    "model": "domain-adapter",
    "messages": [
        {"role": "user", "content": [
            {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_base64}"}},
            {"type": "text", "text": f"Analyse this for category: {category}, location: {label}"}
        ]}
    ],
    "max_tokens": 1024
}
response = requests.post(VLLM_API_URL, json=payload, timeout=30,
                          proxies={"http": None, "https": None})
from agents.extensions.models.litellm_model import LitellmModel

model = LitellmModel(
    model="openai/base-vlm-7b",
    base_url="http://10.x.x.x:8000/v1",
    api_key="not-needed"
)
from agents import Agent, Runner, function_tool as tool

@tool
def get_dashboard_snapshot_tool() -> Dict[str, Any]:
    """Get all KPIs in one call. Call this FIRST for any summary question."""
    with SessionLocal() as db:
        return jsonable_encoder(analytics_service.get_dashboard_snapshot(db))

@tool
def get_entity_ranking_tool(top: bool = True) -> List[Dict[str, Any]]:
    """Get ranked entity performance list. top=True for best, False for worst."""
    with SessionLocal() as db:
        return jsonable_encoder(analytics_service.get_performance_ranking(db, top))

overview_agent = Agent(
    name="OverviewAgent",
    instructions="Handle general dashboard and summary questions. Always call get_dashboard_snapshot_tool first.",
    tools=[get_dashboard_snapshot_tool, get_entity_ranking_tool],
    model=model,
)

status_agent = Agent(
    name="StatusAgent",
    instructions="Handle live status and stream/field health questions.",
    tools=[get_stream_status_tool, get_field_status_tool],
    model=model,
)

quality_agent = Agent(
    name="QualityAgent",
    instructions="Handle rejection-rate and data-quality questions.",
    tools=[get_rejection_stats_tool],
    model=model,
)

orchestrator = Agent(
    name="AnalyticsOrchestrator",
    instructions=(
        "Handle all user communication. Route each question to the right specialist tool "
        "and synthesize its result into a plain-text answer. Do not hallucinate metrics; "
        "only report what a specialist returns."
    ),
    tools=[
        overview_agent.as_tool(
            tool_name="overview_expert",
            tool_description="Answers general dashboard and summary questions.",
        ),
        status_agent.as_tool(
            tool_name="status_expert",
            tool_description="Answers live status and stream/field health questions.",
        ),
        quality_agent.as_tool(
            tool_name="quality_expert",
            tool_description="Answers rejection-rate and data-quality questions.",
        ),
    ],
)
CREATE TABLE events_queue (
    event_id UUID,
    entity_id UInt64,
    event_type LowCardinality(String),
    payload String,
    created_at DateTime64(3)
) ENGINE = Kafka
SETTINGS
    kafka_broker_list = 'your-namespace.servicebus.windows.net:9093',
    kafka_topic_list = 'app-events',
    kafka_group_name = 'clickhouse-consumer',
    kafka_format = 'JSONEachRow';

CREATE MATERIALIZED VIEW events_mv TO events AS
SELECT * FROM events_queue;
azureblob://
├── ml-artifacts/
│   ├── lora-adapters/{v1, v2, v3}
│   ├── base-models/base-vlm-7b/
│   └── eval-results/v3/
├── training-data/{raw, processed, annotations}
└── inference-inputs/{year}/{month}/{day}/{entity_id}/{request_id}.bin
import great_expectations as gx

context = gx.get_context()
batch = context.sources.pandas_default.read_json("annotations_batch.jsonl")
suite = context.get_expectation_suite("annotation_schema_v1")
results = context.run_validation_operator(
    "action_list_operator",
    assets_to_validate=[batch],
    run_id="training-v4-ingestion"
)
if not results["success"]:
    raise ValueError(f"Data validation failed: {results['statistics']}")
import mlflow
from unsloth import FastVisionModel

mlflow.set_experiment("domain-lora-finetuning")
with mlflow.start_run(run_name=f"lora-v{adapter_version}") as run:
    model, tokenizer = FastVisionModel.from_pretrained(
        "unsloth/base-vlm-7b",
        load_in_4bit=True,
    )
    model = FastVisionModel.get_peft_model(
        model,
        r=64,
        lora_alpha=128,
        finetune_vision_layers=True,
    )
    mlflow.log_params({
        "base_model": "base-vlm-7b",
        "lora_rank": 64,
        "lora_alpha": 128,
        "epochs": 3,
        "dataset_version": "v4",
    })
    for epoch in range(epochs):
        train_loss = train_one_epoch(model, dataloader)
        mlflow.log_metric("train_loss", train_loss, step=epoch)
    metrics = evaluate(model, eval_dataloader)
    mlflow.log_metrics({"eval_f1": metrics["f1"]})
    mlflow.log_artifacts(output_dir, artifact_path="lora-adapter")
    mlflow.set_tag("promotion_status", "candidate")
# azure-ml-lora-job.yml
$schema: https://azuremlschemas.azureedge.net/latest/commandJob.schema.json
type: command
code: ./training/
command: >
  python finetune_lora_unsloth.py
  --dataset ${{inputs.training_data}}
  --output-dir ${{outputs.lora_adapter}}
  --lora-rank 64 --lora-alpha 128 --epochs 3
inputs:
  training_data:
    type: uri_folder
    path: azureml://datastores/training_blob/paths/domain/v4/
outputs:
  lora_adapter:
    type: uri_folder
    path: azureml://datastores/artifacts_blob/paths/lora-adapters/v4/
environment: azureml:vlm-unsloth-env:1
compute: azureml:gpu-cluster-nc24ads   # single A100, sufficient for Unsloth LoRA fine-tuning
experiment_name: domain-lora-finetuning
from azure.ai.ml.entities import Model

model = Model(
    name="domain-lora-adapter",
    version="4",
    path="azureml://datastores/artifacts_blob/paths/lora-adapters/v4/",
    tags={
        "base_model": "base-vlm-7b",
        "lora_rank": "64",
        "eval_f1": "0.93",   # illustrative
        "promotion_status": "candidate",
    }
)
ml_client.models.create_or_update(model)
def run_eval(adapter_version: str, eval_dataset_version: str):
    candidate = evaluate_adapter(adapter_path=..., eval_data=...)
    production = evaluate_adapter(adapter_path=get_production_adapter_path(), eval_data=...)
    passed = (
        candidate["f1"] >= production["f1"] - 0.01
        and candidate["f1"] >= MIN_F1_THRESHOLD
      )
    return passed, candidate
New annotation batch → Great Expectations validation
    → dataset versioned in Azure ML
    → Azure ML training job (Unsloth, single GPU, spot instance)
    → offline eval vs. production, per-category
    → [pass] tag adapter "production" in registry
    → sync adapter to local disk on the vLLM host
    → rolling restart of vLLM with new --lora-modules path
# .gitlab-ci.yml
stages:
  - validate
  - train
  - eval
  - deploy

validate-data:
  stage: validate
  script:
    - python eval/validate_dataset.py --version $DATASET_VERSION
submit-training:
  stage: train
  needs: [validate-data]
  script:
    - az ml job create --file azure-ml-lora-job.yml
      --set inputs.dataset_version=$DATASET_VERSION
      --workspace-name $AML_WORKSPACE --resource-group $RESOURCE_GROUP
run-eval:
  stage: eval
  needs: [submit-training]
  script:
    - python eval/run_eval.py --version $DATASET_VERSION
    - python scripts/promote_adapter.py --version $DATASET_VERSION
deploy:
  stage: deploy
  needs: [run-eval]
  when: manual
  script:
    - az containerapp update --name vllm-server --resource-group $RESOURCE_GROUP
      --set-env-vars LORA_ADAPTER_VERSION=$DATASET_VERSION

Слой агентов: три специализированных агента и один оркестратор

Разделение обязанностей:

  1. Маршрутизатор/оркестратор — классифицирует намерение и выбирает инструменты
  2. Специалист по получению данных — собирает контекст домена
  3. Специалист по аналитике — генерирует структурированные данные для хранения

Использование общей GPU для обработки данных требует строгого контроля за одновременностью операций и очередями, чтобы интерактивный чат не страдал от выполнения задач в пакетном режиме.

Аналитический бэкенд: ClickHouse

Результаты действий агента, важные для анализа продукта, сохраняются в ClickHouse: время задержки, выбранные инструменты, идентификаторы полученных документов, результаты работы пользователей. Колоночное хранилище лучше подходит для потоков с высокой кардинальностью событий, чем принудительная обработка всего через базу данных OLTP.

Azure Blob Storage: слой связи в MLOps

Точки контроля, наборы данных, артефакты адаптеров и отчеты об оценке хранятся в Blob. Задания на обучение читают и записывают данные здесь; процессы инференса загружают утвержденные адаптеры по версии. Считайте пути к объектам в Blob частью API-контракта между этапами обучения и предоставления услуг.

Пайплайн обучения: Unsloth в Azure ML

Используйте Unsloth для тонкой настройки с целью повышения скорости и эффективности использования памяти на вычислительных ресурсах Azure ML. Отслеживайте ход выполнения заданий с помощью MLflow: гиперпараметры, метрики оценки, URI артефактов. Рассматривайте к использованию только те адаптеры, которые превосходят базовый уровень эффективности на замороженном наборе данных для оценки.

Проверка качества данных перед обучением

Проверьте схему, удалите персональные данные, обеспечьте баланс классов и избегайте утечек информации между этапами обучения и оценки до того, как исчерпаются ресурсы GPU. При наличии ошибок валидации процесс останавливается.

Тонкая настройка с использованием Unsloth, отслеживаемая в MLflow

Записывайте веса адаптеров в Blob с неизменяемыми идентификаторами версий. Конфигурация инференса явно ссылается на эти идентификаторы — в продакшене не используется понятие «последняя версия» без указания конкретной версии.

Безопасная обработка обоих типов задач

  • Отдельные очереди для интерактивных и пакетных задач
  • Лимиты одновременной работы для каждого адаптера
  • Загружайте по умолчанию стандартный адаптер; специализированные адаптеры — по мере необходимости
  • Отображайте информацию об использовании GPU и глубине очереди в том же ClickHouse (или системе метрик)
  • Возвращайте адаптеры к предыдущему состоянию, изменяя указатель конфигурации, а не перезагружая всю виртуальную машину

Выводы

Один GPU может обслуживать две пользовательские интерфейсные панели, если реализованы технологии динамической загрузки LoRA, изоляция очередей и строгий контроль результатов обработки данных в рамках практик MLOps. Комбинация vLLM + Unsloth + Azure ML + Blob + ClickHouse представляет собой практическое решение для команд, которые пока не могут позволить себе несколько графических процессоров для обработки запросов, но в то же время нуждаются в специализированных функциях и аналитике.

Примечание по планированию мощностей: измеряйте количество токенов в секунду при использовании одного адаптера и при использовании N адаптеров в условиях одновременной интерактивной нагрузки. Технология динамической загрузки LoRA не является бесплатной — затраты на загрузку и фрагментация памяти могут свести на нет преимущества использования одного GPU, если трафик пользователя не учитывает различия между классами очередей. Установите строгий лимит на одновременное использование адаптеров и в первую очередь откладывайте обработку пакетных задач при нарушении установленных показателей качества интерактивного сервиса.

Примечание по планированию мощности: измеряйте количество токенов в секунду при использовании одного адаптера и N адаптеров в условиях одновременной интерактивной нагрузки. Технология Demand LoRA не является бесплатной — затраты на загрузку и фрагментация памяти могут свести на нет преимущества использования «одной GPU», если трафик продукта игнорирует категории очередей. Установите строгий лимит на одновременное использование адаптеров и в первую очередь откладывайте обработку пакетных задач при нарушении интерактивных показателей качества.

Примечание по планированию мощности: измеряйте количество токенов в секунду при использовании одного адаптера и N адаптеров в условиях одновременной интерактивной нагрузки. Технология Demand LoRA не является бесплатной — затраты на загрузку и фрагментация памяти могут свести на нет преимущества использования «одной GPU», если трафик продукта игнорирует категории очередей. Установите строгий лимит на одновременное использование адаптеров и в первую очередь откладывайте обработку пакетных задач при нарушении интерактивных показателей качества.

Примечание по планированию мощности: измеряйте количество токенов в секунду при использовании одного адаптера и N адаптеров в условиях одновременной интерактивной нагрузки. Технология Demand LoRA не является бесплатной — затраты на загрузку и фрагментация памяти могут свести на нет преимущества использования «одной GPU», если трафик продукта игнорирует категории очередей. Установите строгий лимит на одновременное использование адаптеров и в первую очередь откладывайте обработку пакетных задач при нарушении интерактивных показателей качества.

Примечание по планированию мощности: измеряйте количество токенов в секунду при использовании одного адаптера и N адаптеров в условиях одновременной интерактивной нагрузки. Технология Demand LoRA не является бесплатной — затраты на загрузку и фрагментация памяти могут свести на нет преимущества использования «одной GPU», если трафик продукта игнорирует категории очередей. Установите строгий лимит на одновременное использование адаптеров и в первую очередь откладывайте обработку пакетных задач при нарушении интерактивных показателей качества.

Примечание по планированию мощности: измеряйте количество токенов в секунду при использовании одного адаптера и N адаптеров в условиях одновременной интерактивной нагрузки. Технология Demand LoRA не является бесплатной — затраты на загрузку и фрагментация памяти могут свести на нет преимущества использования «одной GPU», если трафик продукта игнорирует категории очередей. Установите строгий лимит на одновременное использование адаптеров и в первую очередь откладывайте обработку пакетных задач при нарушении интерактивных показателей качества.

Примечание по планированию мощности: измеряйте количество токенов в секунду при использовании одного адаптера и N адаптеров в условиях одновременной интерактивной нагрузки. Технология Demand LoRA не является бесплатной — затраты на загрузку и фрагментация памяти могут свести на нет преимущества использования «одной GPU», если трафик продукта игнорирует категории очередей. Установите строгий лимит на одновременное использование адаптеров и в первую очередь откладывайте обработку пакетных задач при нарушении интерактивных показателей качества.

Примечание по планированию мощности: измеряйте количество токенов в секунду при использовании одного адаптера и N адаптеров в условиях одновременной интерактивной нагрузки. Технология Demand LoRA не является бесплатной — затраты на загрузку и фрагментация памяти могут свести на нет преимущества использования «одной GPU», если трафик продукта игнорирует категории очередей. Установите строгий лимит на одновременное использование адаптеров и в первую очередь откладывайте обработку пакетных задач при нарушении интерактивных показателей качества.

Примечание по планированию мощности: измеряйте количество токенов в секунду при использовании одного адаптера и N адаптеров в условиях одновременной интерактивной нагрузки. Технология Demand LoRA не является бесплатной — затраты на загрузку и фрагментация памяти могут свести на нет преимущества использования «одной GPU», если трафик продукта игнорирует категории очередей. Установите строгий лимит на одновременное использование адаптеров и в первую очередь откладывайте обработку пакетных задач при нарушении интерактивных показателей качества.

Примечание по планированию мощности: измеряйте количество токенов в секунду при использовании одного адаптера и N адаптеров в условиях одновременной интерактивной нагрузки. Технология Demand LoRA не является бесплатной — затраты на загрузку и фрагментация памяти могут свести на нет преимущества использования «одной GPU», если трафик продукта игнорирует категории очередей. Установите строгий лимит на одновременное использование адаптеров и в первую очередь откладывайте обработку пакетных задач при нарушении интерактивных показателей качества.

Примечание по планированию мощности: измеряйте количество токенов в секунду при использовании одного адаптера и N адаптеров в условиях одновременной интерактивной нагрузки. Технология Demand LoRA не является бесплатной — затраты на загрузку и фрагментация памяти могут свести на нет преимущества использования «одной GPU», если трафик продукта игнорирует категории очередей. Установите строгий лимит на одновременное использование адаптеров и в первую очередь откладывайте обработку пакетных задач при нарушении интерактивных показателей качества.

Примечание по планированию мощности: измеряйте количество токенов в секунду при использовании одного адаптера и N адаптеров в условиях одновременной интерактивной нагрузки. Технология Demand LoRA не является бесплатной — затраты на загрузку и фрагментация памяти могут свести на нет преимущества использования «одной GPU», если трафик продукта игнорирует категории очередей. Установите строгий лимит на одновременное использование адаптеров и в первую очередь откладывайте обработку пакетных задач при нарушении интерактивных показателей качества.

Примечание по планированию мощности: измеряйте количество токенов в секунду при использовании одного адаптера и N адаптеров в условиях одновременной интерактивной нагрузки. Технология Demand LoRA не является бесплатной — затраты на загрузку и фрагментация памяти могут свести на нет преимущества использования «одной GPU», если трафик продукта игнорирует категории очередей. Установите строгий лимит на одновременное использование адаптеров и в первую очередь откладывайте обработку пакетных задач при нарушении интерактивных показателей качества.

Примечание по планированию мощности: измеряйте количество токенов в секунду при использовании одного адаптера и при использовании N адаптеров в условиях одновременной интерактивной нагрузки. Сервис Demand LoRA не является бесплатным — затраты на загрузку и фрагментация памяти могут свести на нет преимущества использования «одной GPU», если трафик продукта игнорирует категории очередей. Установите строгий лимит на одновременное использование адаптеров и в первую очередь откладывайте выполнение пакетных задач при нарушении интерактивных показателей качества.