Chapter 8. Process batch inference jobs with Distributed Inference with llm-d


You can use batch inference with Distributed Inference with llm-d to process large volumes of inference requests asynchronously. Batch inference enables fire-and-forget job submission through the OpenAI-compatible /v1/batches and /v1/files API, with durable job state and priority-aware scheduling that protects real-time inference service level objectives (SLOs).

Important

Batch inference for Distributed Inference with llm-d is a Technology Preview feature only. Technology Preview features are not supported with Red Hat production service level agreements (SLAs) and might not be functionally complete. Red Hat does not recommend using them in production. These features provide early access to upcoming product features, enabling customers to test functionality and provide feedback during the development process.

For more information about the support scope of Red Hat Technology Preview features, see Technology Preview Features Support Scope.

You can use batch inference with Distributed Inference with llm-d to submit large volumes of inference requests as asynchronous jobs and retrieve results later, without maintaining active connections. The batch inference subsystem persists jobs across gateway pod restarts and uses priority-aware scheduling to run batch workloads during low-utilization periods, protecting real-time inference service level objectives (SLOs) on shared GPU infrastructure.

When to use batch inference

Use batch inference for workloads where immediate responses are not required and you need to maximize throughput across a large volume of requests while meeting defined completion time targets.

Batch inference enables use cases including the following:

  • Autonomous background agents performing multi-step reasoning and deep research
  • Offline evaluations
  • Dataset processing
  • Embedding generation
  • Large-scale model evaluation

All of these use cases follow the same workflow: upload a JSONL input file, create a batch job, monitor progress, and download results. The specific use case is determined by the content and structure of the JSONL input file you provide.

Batch inference provides the following benefits:

  • Increases GPU infrastructure use by filling capacity during periods of lower interactive traffic
  • Protects real-time inference SLOs by running batch workloads at lower priority without interfering with interactive traffic
  • Enables cost-optimized processing by taking advantage of differential billing between batch and interactive workloads

Inference modes in Distributed Inference with llm-d

Distributed Inference with llm-d supports two inference modes that address different latency and throughput requirements:

Real-time inference
Synchronous request-response with latency on the order of seconds to minutes. Model servers process requests immediately. Use real-time inference for interactive applications such as chatbots, code completion, and live search.
Batch inference
Asynchronous fire-and-forget job submission with latency on the order of hours. You can submit requests as batch jobs through the OpenAI-compatible /v1/batches API. Requests are processed in the background. Use batch inference for high-volume workloads that do not require immediate responses, such as embedding generation and model evaluation.

Batch inference architecture

The batch inference subsystem consists of the following components:

Batch gateway API server
Exposes the OpenAI-compatible /v1/batches and /v1/files endpoints. Receives batch job submissions, validates input files, and stores job metadata. The API server is deployed and managed by the Batch Gateway Operator.
Batch processor

Reads pending batch jobs from a queue, concurrently dispatches inference requests from each job to model servers through the internal ClusterIP gateway, and writes results to S3-compatible storage. The processor uses AIMD (additive-increase / multiplicative-decrease) adaptive concurrency control to dynamically adjust concurrent request limits based on success and failure signals, automatically backing off under load to protect interactive traffic. Retry logic with exponential backoff handles transient failures.

To maximize throughput, the processor sorts requests by system-prompt hash before dispatching, optimizing prefix cache reuse across the serving pool by keeping identical-prefix KV-cache entries hot. The processor integrates with Distributed Inference with llm-d’s intelligent request routing and flow control mechanisms, allowing batch workloads to benefit from prefix-cache-aware routing and automatic load balancing.

Garbage collector
Periodically cleans up completed, failed, and expired batch jobs based on configurable retention periods. Removes associated input and output files from storage.

The batch inference subsystem uses a pluggable storage architecture with two layers:

  • A database layer for persisting batch metadata, including job state, request counts, and token usage
  • An exchange layer for job queuing, priority queues, event channels, and in-flight job tracking

The available storage plugins are PostgreSQL for the database layer and Redis or Valkey for the exchange layer. The subsystem also requires S3-compatible storage or filesystem storage for batch input and output files.

OpenAI Batch API compatibility

The batch inference API is compatible with the OpenAI Batch API specification. You submit batch jobs by uploading a JSONL input file through the /v1/files endpoint and creating a batch job through the /v1/batches endpoint. The batch inference subsystem supports the /v1/chat/completions, /v1/completions, /v1/embeddings, /v1/responses, and /v1/moderations inference endpoints within batch requests.

Each line in the JSONL input file has one inference request with a unique custom_id that correlates the request to its result in the output file. The API server validates the input file format before scheduling the job.

By default, batches are limited to 50,000 requests and input files are limited to 200 MB. These limits are configurable. Optionally, you can apply a RateLimitPolicy on the external gateway to enforce per-user rate limiting for batch API requests.

Priority-aware dispatching for batch workloads

The batch gateway maintains an internal priority queue of jobs sorted by SLO deadline. Each job SLO is computed from its completion_window at creation time, so jobs with the nearest deadline are processed first.

You can use shared GPU infrastructure for both real-time and batch workloads without dedicated GPU pools for each workload type. To achieve this, configure the batch gateway to work with the flow control mechanism in Distributed Inference with Distributed Inference with llm-d. In this setup, the SLOs of real-time workloads are protected while batch workloads make progress toward completion by utilizing free GPU capacity. When dispatching inference requests, the batch processor sets flow control headers that communicate the job SLO deadline and priority band to the router. The priority band is determined by a configurable InferenceObjective CRD that you specify in the model gateway configuration. Batch workloads are typically assigned a lower priority band than interactive workloads, so that they are dispatched only when capacity is available.

Multi-tenancy

The batch inference subsystem supports multi-tenancy through the X-MaaS-Username header. Each user’s batch jobs and files are isolated. Authentication is delegated to the external gateway’s AuthPolicy, which validates bearer tokens in the Authorization header (Kubernetes ServiceAccount tokens or user tokens) and injects the authenticated username into the request headers for the batch gateway to use for tenant identification.

Additional resources

You can deploy and configure the batch inference subsystem for Distributed Inference with llm-d to enable asynchronous batch job submission on shared GPU infrastructure. The batch gateway is deployed through the AI Gateway component in the DataScienceCluster CR and configured with backing services, internal routing for model authorization, and external API routes for batch job submission.

Prerequisites

  • Distributed Inference with llm-d is deployed with Inference Gateway on Red Hat OpenShift AI.
  • The batch gateway component is enabled in the DataScienceCluster resource.
  • You have cluster administrator access.
  • You have installed the OpenShift CLI (`oc`). For more information, see Installing the OpenShift CLI.
  • A Redis or Valkey instance is available for job queuing.

    In disconnected environments, deploy Redis or Valkey cluster-locally.

  • A PostgreSQL instance is available for batch metadata storage.

    In disconnected environments, deploy PostgreSQL cluster-locally.

  • S3-compatible storage, such as AWS S3, MinIO, or Ceph, is available for batch input and output files.

    In disconnected environments, deploy MinIO or another S3-compatible storage service cluster-locally.

Procedure

  1. Verify that the batch gateway component is enabled in the DataScienceCluster resource:

    $ oc get datasciencecluster default-dsc -o yaml

    Expected output:

    spec:
      components:
        aigateway:
          managementState: Managed
          batchGateway:
            managementState: Managed

    If the batch gateway component is not enabled, edit the DataScienceCluster resource:

    $ oc edit datasciencecluster default-dsc

    Add or update the following configuration:

    spec:
      components:
        aigateway:
          managementState: Managed
          batchGateway:
            managementState: Managed

    Save the changes and wait for the DataScienceCluster to reconcile. The operator creates the batch gateway namespace and deploys the Batch Gateway Operator.

  2. Create an internal ClusterIP gateway for batch processor inference routing.

    The batch processor routes inference requests through this internal gateway to enforce model-level authorization while bypassing per-user token rate limits on the external gateway.

    Save the following YAML to a file named batch-internal-gateway.yaml:

    apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
      name: batch-internal-gateway
      namespace: openshift-ingress
      annotations:
        networking.istio.io/service-type: ClusterIP
    spec:
      gatewayClassName: openshift-default
      listeners:
      - name: http
        port: 80
        protocol: HTTP
        allowedRoutes:
          namespaces:
            from: Selector
            selector:
              matchLabels:
                llm-d.ai/gateway-route: "true"

    Apply the Gateway resource:

    $ oc apply -f batch-internal-gateway.yaml
    Important

    The networking.istio.io/service-type: ClusterIP annotation ensures the gateway is not externally accessible. The internal gateway uses HTTP only because traffic is cluster-internal.

  3. Create an HTTPRoute to route batch processor inference requests to the model server through the internal gateway.

    The batch processor rewrites the request path to match the model server’s API and includes the user’s original authentication token.

    Save the following YAML to a file named batch-llm-route.yaml:

    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: batch-llm-route
      namespace: <llmisvc_namespace>
    spec:
      parentRefs:
      - name: batch-internal-gateway
        namespace: openshift-ingress
      rules:
      - matches:
        - path:
            type: PathPrefix
            value: /<llmisvc_namespace>/<llmisvc_name>/v1/chat/completions
        filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /v1/chat/completions
        backendRefs:
        - group: inference.networking.k8s.io
          kind: InferencePool
          name: <inference_pool_name>
          port: 80
      - matches:
        - path:
            type: PathPrefix
            value: /<llmisvc_namespace>/<llmisvc_name>/v1/completions
        filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /v1/completions
        backendRefs:
        - group: inference.networking.k8s.io
          kind: InferencePool
          name: <inference_pool_name>
          port: 80
      - matches:
        - path:
            type: PathPrefix
            value: /<llmisvc_namespace>/<llmisvc_name>/v1/embeddings
        filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /v1/embeddings
        backendRefs:
        - group: inference.networking.k8s.io
          kind: InferencePool
          name: <inference_pool_name>
          port: 80
      - matches:
        - path:
            type: PathPrefix
            value: /<llmisvc_namespace>/<llmisvc_name>/v1/responses
        filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /v1/responses
        backendRefs:
        - group: inference.networking.k8s.io
          kind: InferencePool
          name: <inference_pool_name>
          port: 80
      - matches:
        - path:
            type: PathPrefix
            value: /<llmisvc_namespace>/<llmisvc_name>/v1/moderations
        filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /v1/moderations
        backendRefs:
        - group: inference.networking.k8s.io
          kind: InferencePool
          name: <inference_pool_name>
          port: 80

    where:

    <llmisvc_namespace>
    Specifies the namespace where the LLMInferenceService is deployed.
    <llmisvc_name>
    Specifies the name of the LLMInferenceService resource.
    <inference_pool_name>
    Specifies the name of the InferencePool resource created by the LLMInferenceService.
  4. Apply the HTTPRoute resource:

    $ oc apply -f batch-llm-route.yaml
  5. Create an AuthPolicy for the batch-llm-route to enforce model-level authorization:

    Save the following YAML to a file named batch-llm-authpolicy.yaml:

    apiVersion: kuadrant.io/v1
    kind: AuthPolicy
    metadata:
      name: batch-llm-authpolicy
      namespace: <llmisvc_namespace>
    spec:
      targetRef:
        group: gateway.networking.k8s.io
        kind: HTTPRoute
        name: batch-llm-route
      rules:
        authentication:
          "kubernetes-token":
            kubernetesTokenReview:
              audiences:
              - "https://kubernetes.default.svc"
        authorization:
          "llmisvc-access":
            kubernetesSubjectAccessReview:
              user:
                selector: auth.identity.user.username
              resourceAttributes:
                group: serving.kserve.io
                resource: llminferenceservices
                name: <llmisvc_name>
                namespace: <llmisvc_namespace>
                verb: get
        response:
          success:
            headers:
              "X-MaaS-Username":
                plain:
                  selector: auth.identity.user.username

    Apply the AuthPolicy resource:

    $ oc apply -f batch-llm-authpolicy.yaml

    The AuthPolicy validates the user’s Kubernetes token and checks whether the user has permission to get the specific LLMInferenceService resource using SubjectAccessReview.

  6. Create a Kubernetes Secret with connection details for Redis, PostgreSQL, and S3-compatible storage.

    Save the following YAML to a file named batch-gateway-secrets.yaml:

    apiVersion: v1
    kind: Secret
    metadata:
      name: batch-gateway-secrets
      namespace: <batch_gateway_namespace>
    type: Opaque
    stringData:
      redis-url: "redis://<redis_host>:<redis_port>/0"
      postgresql-url: "postgresql://<db_user>:<db_password>@<db_host>:<db_port>/<db_name>?sslmode=disable"
      s3-secret-access-key: "<s3_secret_key>"

    where:

    <batch_gateway_namespace>
    Specifies the namespace for the batch gateway deployment.
    <redis_host>
    Specifies the hostname or IP address of the Redis or Valkey instance.
    <redis_port>
    Specifies the port number of the Redis or Valkey instance (default: 6379).
    <db_user>
    Specifies the PostgreSQL username.
    <db_password>
    Specifies the PostgreSQL password.
    <db_host>
    Specifies the hostname or IP address of the PostgreSQL instance.
    <db_port>
    Specifies the port number of the PostgreSQL instance (default: 5432).
    <db_name>
    Specifies the name of the PostgreSQL database for batch metadata.
    <s3_secret_key>

    Specifies the secret access key for S3-compatible storage.

    Note

    S3 configuration settings (region, endpoint, access key ID, and bucket name) are non-sensitive and are configured in the LLMBatchGateway custom resource, not in this secret. Only the S3 secret access key belongs in the secret.

    Apply the Secret:

    $ oc apply -f batch-gateway-secrets.yaml
  7. Deploy infrastructure dependencies for the batch gateway.

    The batch gateway requires Redis or Valkey for job queuing, PostgreSQL for metadata storage, and S3-compatible storage for batch files. Deploy these services before creating the LLMBatchGateway custom resource.

    For production deployments, use managed services or deploy these components with appropriate persistence, backups, and high availability. For development or testing environments, you can deploy minimal instances using Helm charts or Kubernetes manifests.

    Note

    For example deployment manifests for Redis, PostgreSQL, and MinIO, see the batch gateway RHOAI deployment guide.

  8. Create an LLMBatchGateway custom resource to deploy the batch gateway components.

    Save the following YAML to a file named llm-batch-gateway.yaml:

    apiVersion: llm-d.ai/v1alpha1
    kind: LLMBatchGateway
    metadata:
      name: batch-gateway
      namespace: <batch_gateway_namespace>
    spec:
      secretRef:
        name: batch-gateway-secrets
      dbBackend: postgresql
      fileStorage:
        s3:
          region: <s3_region>
          endpoint: <s3_endpoint>
          accessKeyId: <s3_access_key_id>
          prefix: <s3_bucket_name>
          usePathStyle: true
          autoCreateBucket: true
      apiServer:
        replicas: 1
      processor:
        replicas: 1
        globalInferenceGateway:
          url: http://batch-internal-gateway.openshift-ingress.svc.cluster.local/<llmisvc_namespace>/<llmisvc_name>/v1
          requestTimeout: 5m
          maxRetries: 3
          initialBackoff: 1s
          maxBackoff: 60s
        config:
          inferenceObjective: batch-workload
      gc:
        interval: 30m
      tls:
        enabled: true
        certManager:
          issuerName: selfsigned-issuer
          issuerKind: ClusterIssuer
          dnsNames:
          - batch-gateway-apiserver
          - batch-gateway-apiserver.<batch_gateway_namespace>.svc.cluster.local
          - localhost

    where:

    <batch_gateway_namespace>
    Specifies the namespace where the batch gateway is deployed.
    <s3_region>
    Specifies the S3 region (for example, us-east-1).
    <s3_endpoint>
    Specifies the S3 endpoint URL. For MinIO deployed in-cluster, use http://minio.<batch_gateway_namespace>.svc.cluster.local:9000.
    <s3_access_key_id>
    Specifies the S3 access key ID.
    <s3_bucket_name>
    Specifies the S3 bucket name for batch input and output files.
    <llmisvc_namespace>
    Specifies the namespace of the LLMInferenceService.
    <llmisvc_name>

    Specifies the name of the LLMInferenceService resource.

    Apply the LLMBatchGateway resource:

    $ oc apply -f llm-batch-gateway.yaml

    The Batch Gateway Operator deploys the batch-gateway-apiserver, batch-processor, and garbage-collector components. Component images are pinned by the operator from its deployment configuration.

    Note

    To use filesystem storage instead of S3, replace the fileStorage.s3 section with:

    fileStorage:
      fs:
        basePath: /tmp/batch-gateway
        claimName: <your-pvc-name>

    The PersistentVolumeClaim must have ReadWriteMany access mode.

  9. Create an HTTPRoute to route external batch API traffic to the batch-gateway-apiserver.

    Save the following YAML to a file named batch-api-route.yaml:

    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: batch-api-route
      namespace: <batch_gateway_namespace>
    spec:
      parentRefs:
      - name: <external_gateway_name>
        namespace: <gateway_namespace>
      rules:
      - matches:
        - path:
            type: PathPrefix
            value: /v1/batches
        - path:
            type: PathPrefix
            value: /v1/files
        backendRefs:
        - name: batch-gateway-apiserver
          port: 8000

    where:

    <external_gateway_name>
    Specifies the name of the external Kubernetes Gateway resource that receives incoming batch API requests.
    <gateway_namespace>

    Specifies the namespace of the external gateway (typically openshift-ingress).

    Apply the HTTPRoute resource:

    $ oc apply -f batch-api-route.yaml

    Apply an AuthPolicy for the batch API routes.

    Save the following YAML to a file named batch-api-authpolicy.yaml:

    apiVersion: kuadrant.io/v1
    kind: AuthPolicy
    metadata:
      name: batch-api-authpolicy
      namespace: <batch_gateway_namespace>
    spec:
      targetRef:
        group: gateway.networking.k8s.io
        kind: HTTPRoute
        name: batch-api-route
      rules:
        authentication:
          "kubernetes-token":
            kubernetesTokenReview:
              audiences:
              - "https://kubernetes.default.svc"
        response:
          success:
            headers:
              "X-MaaS-Username":
                plain:
                  selector: auth.identity.user.username

    Apply the AuthPolicy resource:

    $ oc apply -f batch-api-authpolicy.yaml

    The batch API AuthPolicy performs authentication only. Model-level authorization is enforced by the batch-llm-authpolicy on the internal gateway when the processor forwards inference requests.

  10. Apply a RateLimitPolicy to enforce per-user rate limits on the batch API.

    Save the following YAML to a file named batch-rate-limit-policy.yaml:

    apiVersion: kuadrant.io/v1
    kind: RateLimitPolicy
    metadata:
      name: batch-rate-limit
      namespace: <batch_gateway_namespace>
    spec:
      targetRef:
        group: gateway.networking.k8s.io
        kind: HTTPRoute
        name: batch-api-route
      limits:
        "per-user":
          rates:
          - limit: 20
            window: 1m
          counters:
          - auth.identity.user.username

    Apply the RateLimitPolicy resource:

    $ oc apply -f batch-rate-limit-policy.yaml
  11. Create an InferenceObjective at priority -1 for batch workloads.

    Save the following YAML to a file named batch-inference-objective.yaml:

    apiVersion: llm-d.ai/v1alpha2
    kind: InferenceObjective
    metadata:
      name: batch-workload
      namespace: <llmisvc_namespace>
    spec:
      priority: -1
      poolRef:
        group: inference.networking.k8s.io
        kind: InferencePool
        name: <inference_pool_name>

    where:

    <llmisvc_namespace>
    Specifies the namespace where the InferencePool is deployed.
    <inference_pool_name>

    Specifies the name of the InferencePool resource for the target model.

    Apply the InferenceObjective resource:

    $ oc apply -f batch-inference-objective.yaml

    The negative priority value (priority: -1) means batch requests run at lower priority than interactive workloads and are the first to be dropped when the system reaches saturation, protecting real-time inference SLOs.

Verification

  1. Verify that the batch gateway pods are running:

    $ oc get pods -n <batch_gateway_namespace>

    The output shows pods for the batch-gateway-apiserver, batch-gateway-processor, and batch-gateway-gc components in Running status.

  2. Submit a test batch job to verify the subsystem is functioning:

    1. Create a test JSONL input file:

      $ cat > /tmp/test-batch.jsonl << 'EOF'
      {"custom_id": "test-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "<model_name>", "messages": [{"role": "user", "content": "Hello"}], "max_tokens": 10}}
      EOF
    2. Upload the file:

      $ curl -s -X POST "https://<batch_gateway_url>/v1/files" \
        -H "Authorization: Bearer <auth_token>" \
        -F "file=@/tmp/test-batch.jsonl" \
        -F "purpose=batch"
    3. Create a batch job by using the returned file_id:

      $ curl -s -X POST "https://<batch_gateway_url>/v1/batches" \
        -H "Authorization: Bearer <auth_token>" \
        -H "Content-Type: application/json" \
        -d '{
          "input_file_id": "<file_id>",
          "endpoint": "/v1/chat/completions",
          "completion_window": "24h"
        }'
    4. Poll the batch status until it reaches completed:

      $ curl -s "https://<batch_gateway_url>/v1/batches/<batch_id>" \
        -H "Authorization: Bearer <auth_token>"

      A successful test batch job transitions through validating, in_progress, finalizing, and completed states.

8.3. Submit a batch inference job

You can submit a batch inference job to process large volumes of inference requests asynchronously through the OpenAI-compatible /v1/batches and /v1/files API endpoints. Batch jobs persist across gateway pod restarts and do not require an active client connection after submission.

Prerequisites

  • The batch inference subsystem is configured by a cluster administrator.
  • You have an authentication token for the batch-gateway endpoint: a Kubernetes ServiceAccount token or a user token.
  • A deployed Distributed Inference with llm-d model is available for inference.
  • You have the batch-gateway API endpoint URL.

Procedure

  1. Prepare a JSONL input file with one inference request per line.

    Each line must be a valid JSON object with the following fields:

    {"custom_id": "request-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "<model_name>", "messages": [{"role": "user", "content": "Summarize the benefits of container orchestration."}], "max_tokens": 256}}
    {"custom_id": "request-2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "<model_name>", "messages": [{"role": "user", "content": "Explain Kubernetes networking."}], "max_tokens": 256}}

    where:

    custom_id
    Specifies a unique identifier for correlating this request with its result in the output file.
    method
    Specifies the HTTP method. Must be POST.
    url
    Specifies the inference endpoint path. Supported values are /v1/chat/completions, /v1/completions, /v1/embeddings, /v1/responses, and /v1/moderations.
    body

    Specifies the request body, following the same schema as the corresponding synchronous inference endpoint.

    Note

    This example shows chat completion requests. You can use this same workflow for different use cases such as embedding generation, offline evaluations, or dataset processing by adjusting the content and structure of your JSONL file. For example, use /v1/embeddings for embedding generation or construct evaluation-specific prompts for offline model evaluation.

    Save this file locally, for example as batch-input.jsonl.

  2. Upload the input file to the batch gateway:

    $ curl -s -X POST "https://<batch_gateway_url>/v1/files" \
      -H "Authorization: Bearer <auth_token>" \
      -F "file=@batch-input.jsonl" \
      -F "purpose=batch"

    where:

    <batch_gateway_url>
    Specifies the URL of the batch-gateway-apiserver endpoint.
    <auth_token>

    Specifies your Kubernetes ServiceAccount token or user token.

    The response includes a file_id that you use to create the batch job:

    {
      "id": "file-abc123",
      "object": "file",
      "purpose": "batch",
      "filename": "batch-input.jsonl",
      "bytes": 512,
      "created_at": 1719878400
    }
    Note

    For additional file operations such as listing files, retrieving file metadata, or deleting files, see Section 8.4, “Batch inference API reference”.

  3. Create a batch job by using the uploaded file:

    $ curl -s -X POST "https://<batch_gateway_url>/v1/batches" \
      -H "Authorization: Bearer <auth_token>" \
      -H "Content-Type: application/json" \
      -d '{
        "input_file_id": "<file_id>",
        "endpoint": "/v1/chat/completions",
        "completion_window": "24h"
      }'

    where:

    <file_id>

    Specifies the id value from the file upload response.

    The response includes a batch_id and shows the initial status as validating:

    {
      "id": "batch-xyz789",
      "object": "batch",
      "endpoint": "/v1/chat/completions",
      "input_file_id": "file-abc123",
      "status": "validating",
      "created_at": 1719878400
    }
    Note

    This example shows required parameters only. For optional parameters such as custom metadata or batch listing and filtering options, see Section 8.4, “Batch inference API reference”.

  4. Poll the batch job status until it reaches completed:

    $ curl -s "https://<batch_gateway_url>/v1/batches/<batch_id>" \
      -H "Authorization: Bearer <auth_token>"

    where:

    <batch_id>

    Specifies the id value from the batch creation response.

    The batch job transitions through the following states: validating in_progress finalizing completed. A completed batch response includes output_file_id and request counts:

    {
      "id": "batch-xyz789",
      "object": "batch",
      "status": "completed",
      "input_file_id": "file-abc123",
      "output_file_id": "file-out456",
      "request_counts": {
        "total": 2,
        "completed": 2,
        "failed": 0
      }
    }
  5. Retrieve the batch results by downloading the output file:

    $ curl -s "https://<batch_gateway_url>/v1/files/<output_file_id>/content" \
      -H "Authorization: Bearer <auth_token>"

    where:

    <output_file_id>

    Specifies the output_file_id value from the completed batch response.

    The output is a JSONL file with one result per line, correlated by custom_id:

    {"id": "response-1", "custom_id": "request-1", "response": {"status_code": 200, "body": {"choices": [{"message": {"role": "assistant", "content": "Container orchestration provides..."}}]}}}
    {"id": "response-2", "custom_id": "request-2", "response": {"status_code": 200, "body": {"choices": [{"message": {"role": "assistant", "content": "Kubernetes networking..."}}]}}}
  6. Optional: Cancel a batch job:

    $ curl -s -X POST "https://<batch_gateway_url>/v1/batches/<batch_id>/cancel" \
      -H "Authorization: Bearer <auth_token>"

    You can cancel a batch job in the validating or in_progress state. Jobs still queued in the validating state are cancelled immediately and transition directly to cancelled. Jobs already being processed in the in_progress state transition through cancelling to cancelled as the processor winds down. Requests that were already completed before cancellation are included in the output file.

Verification

  • Verify that the batch job completed successfully by checking the status field in the batch response. A successful batch shows status: completed with request_counts.failed: 0.
  • Verify that the output file contains results for all input requests by comparing the number of lines in the output file with the request_counts.total value.

Additional resources

8.4. Batch inference API reference

The batch inference subsystem for Distributed Inference with llm-d exposes OpenAI-compatible REST API endpoints for submitting and managing asynchronous batch inference jobs. This reference documents all available endpoints, parameters, response schemas, and error formats.

Consult this reference when you need to the following:

  • Use optional parameters not covered in the basic workflow, such as metadata or filtering options
  • Understand the complete Batch object schema and lifecycle states
  • Troubleshoot API errors by reviewing status codes and error response formats
  • Integrate batch inference into automated workflows or scripts

All requests require a bearer token in the Authorization header. Use a Kubernetes ServiceAccount token or a user token validated by the gateway AuthPolicy.

/v1/batches endpoints

The /v1/batches endpoints manage batch inference jobs.

Expand
Table 8.1. POST /v1/batches — Create a batch job
FieldDescription

Request body

 

input_file_id (required)

The ID of the uploaded JSONL input file. The file purpose field must be set to batch.

endpoint (required)

The inference endpoint to use for all requests in the batch. Supported values: /v1/chat/completions, /v1/completions, /v1/embeddings, /v1/responses, /v1/moderations.

completion_window (required)

The time window for batch completion. Accepts any valid Go duration string, such as "1h", "30m", "24h", or "48h".

metadata (optional)

A map of key-value pairs for custom metadata. Maximum 16 pairs; keys up to 64 characters, values up to 512 characters.

Response

A Batch object with status: validating.

Expand
Table 8.2. GET /v1/batches/{batch_id} — Retrieve batch status
FieldDescription

Path parameter

 

batch_id (required)

The ID of the batch to retrieve.

Response

A Batch object with current status, request counts, and file IDs.

Expand
Table 8.3. GET /v1/batches — List batches
FieldDescription

Query parameters

 

limit (optional)

Maximum number of batches to return. Default: 20, maximum: 100.

after (optional)

An integer offset for pagination. Returns batches starting from this position in the result set.

Response

A list of Batch objects for the authenticated user.

Expand
Table 8.4. POST /v1/batches/{batch_id}/cancel — Cancel a batch
FieldDescription

Path parameter

 

batch_id (required)

The ID of the batch to cancel. The batch must be in validating, in_progress, or cancelling status.

Response

A Batch object with status: cancelling (if the job was in progress) or status: cancelled (if the job was still queued).

/v1/files endpoints for batch inference

The /v1/files endpoints in the batch inference context manage JSONL input and output files.

Expand
Table 8.5. POST /v1/files — Upload a file
FieldDescription

Form data

 

file (required)

The JSONL file to upload. Maximum size: 200 MB.

purpose (required)

Must be batch.

Response

A File object with id, filename, bytes, purpose, and created_at.

Expand
Table 8.6. GET /v1/files — List files
FieldDescription

Query parameters

 

limit (optional)

Maximum number of files to return. Default: 20, maximum: 10,000.

after (optional)

An integer offset for pagination. Returns files starting from this position in the result set.

Response

A list wrapper with data (array of File objects), has_more (boolean), first_id, and last_id.

Expand
Table 8.7. GET /v1/files/{file_id} — Retrieve file metadata
FieldDescription

Path parameter

 

file_id (required)

The ID of the file to retrieve metadata for.

Response

A File object with metadata including id, filename, bytes, purpose, and created_at.

Expand
Table 8.8. GET /v1/files/{file_id}/content — Download file content
FieldDescription

Path parameter

 

file_id (required)

The ID of the file whose content to download. Use this to retrieve batch output files.

Response

The raw JSONL file content.

Expand
Table 8.9. DELETE /v1/files/{file_id} — Delete a file
FieldDescription

Path parameter

 

file_id (required)

The ID of the file to delete.

Response

A deletion confirmation with the file ID and deleted: true.

JSONL input format

Each line in the input JSONL file must be a valid JSON object with the following fields:

Expand
Table 8.10. JSONL input line schema
FieldTypeRequiredDescription

custom_id

string

Yes

A unique identifier for this request. Used to correlate the request with its result in the output file.

method

string

Yes

The HTTP method. Must be POST.

url

string

Yes

The inference endpoint path. Supported values: /v1/chat/completions, /v1/completions, /v1/embeddings, /v1/responses, /v1/moderations.

body

object

Yes

The request body, following the same schema as the corresponding synchronous inference endpoint. Must include the model field.

Example input line:

{"custom_id": "req-001", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "<model_name>", "messages": [{"role": "user", "content": "What is Kubernetes?"}], "max_tokens": 100}}

JSONL output format

Each line in the output JSONL file contains the result for one input request:

Expand
Table 8.11. JSONL output line schema
FieldTypeDescription

id

string

A unique identifier for this response.

custom_id

string

The custom_id from the corresponding input request, used for correlation.

response

object

Contains status_code (integer) and body (object). The body follows the same schema as the corresponding synchronous inference endpoint response.

error

object or null

If the request failed, contains code (string) and message (string). Null for successful requests.

Batch object schema

The Batch object represents a batch inference job and is returned by all /v1/batches endpoints.

Note

This table shows principal Batch object fields. For the complete schema including all timestamp fields, usage statistics, and error details, see the OpenAI Batch API reference.

Expand
Table 8.12. Batch object fields
FieldTypeDescription

id

string

The unique identifier for the batch.

object

string

Always batch.

endpoint

string

The inference endpoint used for this batch.

input_file_id

string

The ID of the input JSONL file.

output_file_id

string or null

The ID of the output JSONL file. Available when the batch reaches completed status.

error_file_id

string or null

The ID of the error file containing failed requests. Available when the batch completes with errors.

status

string

The current lifecycle state of the batch.

request_counts

object

Contains total, completed, and failed counts for requests in the batch.

metadata

object or null

Custom key-value pairs set when creating the batch.

created_at

integer

Unix timestamp of batch creation.

in_progress_at

integer or null

Unix timestamp when the batch started processing.

completed_at

integer or null

Unix timestamp when the batch completed.

failed_at

integer or null

Unix timestamp when the batch failed.

cancelled_at

integer or null

Unix timestamp when the batch was cancelled.

expired_at

integer or null

Unix timestamp when the batch expired.

Batch lifecycle states

A batch job transitions through the following states:

Expand
Table 8.13. Batch lifecycle state machine
StateDescription

validating

The input file is being validated. The batch transitions to in_progress if validation succeeds, or to failed if the input file is malformed.

in_progress

Individual inference requests are being processed by the batch processor. The batch remains in this state until all requests are completed or the batch is cancelled.

finalizing

All requests have been processed. The output file is being assembled and uploaded to storage.

completed

The batch finished successfully. The output_file_id is set and results are available for download.

failed

The batch failed due to validation errors or unrecoverable processing errors. Check the error_file_id for details.

expired

The batch did not complete within the completion_window.

cancelling

A cancellation request has been received. The processor stops dispatching new requests.

cancelled

The batch was cancelled. Requests completed before cancellation are included in the output file.

Rate limits and scale limits

Expand
Table 8.14. Batch inference limits
LimitDefault valueDescription

Maximum requests per batch

50,000

The default maximum number of inference request lines in a single JSONL input file. This limit is configurable.

Maximum input file size

200 MB

The default maximum size of an uploaded JSONL input file. This limit is configurable.

Optionally, you can apply a RateLimitPolicy on the external gateway to enforce per-user rate limiting for batch API requests. The rate limit value is defined in the policy configuration.

Additional resources

Red Hat logoGithubredditYoutubeTwitter

Learn

Try, buy, & sell

Communities

About Red Hat

We deliver hardened solutions that make it easier for enterprises to work across platforms and environments, from the core datacenter to the network edge.

Making open source more inclusive

Red Hat is committed to replacing problematic language in our code, documentation, and web properties. For more details, see the Red Hat Blog.

About Red Hat Documentation

Legal Notice

Theme

© 2026 Red Hat
Back to top