LLM batch inference

Updated at:

The Elastic Algorithm Service (EAS) LLM intelligent router supports OpenAI-compatible batch inference, which lets you submit large-scale inference requests for background processing. This is ideal for workloads that don't require real-time responses—offline evaluation, data annotation, and content generation—where asynchronous processing significantly reduces GPU costs and maximizes resource utilization.

How it works

Upload a JSONL input file to an Object Storage Service (OSS) bucket, then submit an asynchronous batch job against the LLM intelligent router. The service splits the input into shards, processes them in parallel against the backend inference service, and writes the output to OSS. Query job status at any time; retrieve results when the job reaches completed.

Prerequisites

Before you begin, ensure that you have:

  • A deployed EAS LLM intelligent router service (standalone or unified deployment)

  • An OSS bucket dedicated to batch inference input and output files

  • The following OSS permissions granted to the service's RAM role: oss:GetObject, oss:ListObjects, oss:PutObject

Files stored in OSS are not deleted automatically and incur ongoing storage charges. Configure OSS lifecycle rules on your batch bucket or prefix to expire old input, output, and error files before you start submitting jobs.

Limits

Resource

Limit

Maximum requests per input file

50,000

Maximum input file size

200 MB

Supported completion_window values

24h only

Supported batch endpoints

/v1/responses, /v1/chat/completions, /v1/embeddings, /v1/completions

Deploy a service with batch inference

Configuration parameters

Configure these parameters in the JSON deployment file. Only JSON-based deployment is supported.

Parameter

Required

Description

llm_gateway.batch_oss_path

Yes

OSS path for input files and output results. Must start with oss:// and include a bucket name and optional prefix—for example, oss://my-batch-bucket/batchllm/. Use a dedicated bucket or prefix to simplify permission management and lifecycle rule configuration.

options.enable_ram_role

Yes

Must be true. Authorizes EAS to access the OSS bucket.

llm_gateway.batch_oss_endpoint

No

OSS endpoint. Defaults to the internal network endpoint of the current region.

llm_gateway.batch_options

No

Performance tuning options. See Performance tuning.

batch_options accepts the following flags as a comma-separated string:

Flag

Default

Description

--batch-parallel

8

Concurrency for processing shards.

--batch-lines-per-shard

1000

Maximum request lines per shard.

--batch-request-timeout

3m

Timeout for a single inference request. Uses Go duration format: 3m, 10s.

--batch-request-retry-times

3

Retry count after a single request fails.

Example: --batch-parallel=10,--batch-lines-per-shard=500,--batch-request-timeout=5m

Deployment options

Choose between two deployment topologies:

  • Standalone deployment: Deploy the LLM intelligent router and the inference service separately, then associate them.

  • Unified deployment: Package both services together in a single JSON configuration.

Standalone deployment

Step 1: Deploy the LLM intelligent router.

In Deploy Custom Model > Deploy with JSON, paste the following JSON and click Deploy. Replace llm_gateway.batch_oss_path, metadata.workspace_id, metadata.group, and metadata.name with your values.

{
    "llm_gateway": {
        "batch_oss_path": "oss://your-bucket/path/to/prefix"
    },
    "llm_scheduler": {
        "cpu": 2,
        "memory": 4000,
        "policy": "prefix-cache"
    },
    "metadata": {
        "cpu": 4,
        "gpu": 0,
        "group": "group_llm_gateway",
        "instance": 2,
        "memory": 8000,
        "name": "llm_gateway",
        "type": "LLMGatewayService",
        "workspace_id": "217**3"
    },
    "options": {
        "enable_ram_role": true
    }
}

Step 2: Deploy an LLM service.

Deploy a Qwen3-8B service or another supported model. For details, see LLM Deployment.

Step 3: Associate the LLM service with the router.

Important

The Service Features > LLM Intelligent Router option on the deployment page does not support routers with batch inference enabled. Instead, after configuring other parameters, go to Service Configuration and click Edit. Add metadata.group to the JSON configuration and set it to the group name used when deploying the LLM intelligent router (for example, group_llm_gateway).

image

Unified deployment

Add options.enable_ram_role and llm_gateway.batch_oss_path to the LLM intelligent router member in the unified JSON configuration:

{
  "metadata": {
    "group": "feitest",
    "name": "feitest",
    "workspace_id": "217123"
  },
  "members": [
    {
      "llm_gateway": {
        "batch_oss_path": "oss://your-bucket/path/to/prefix", // required
        "infer_backend": "vllm"
      },
      "llm_scheduler": {
        "cpu": 2,
        "memory": 4000,
        "policy": "prefix-cache"
      },
      "metadata": {
        "cpu": 4,
        "gpu": 0,
        "group": "group_llm_gateway",
        "instance": 2,
        "memory": 8000,
        "name": "llm_gateway",
        "type": "LLMGatewayService",
        "workspace_id": "217123"
      },
      "options": {
        "enable_ram_role": true // required
      }
    },
    {
      // inference member
    }
  ]
}

Get access credentials

Batch inference requests must be sent to the LLM intelligent router service. Follow these steps to obtain the public endpoint and token.

  1. On the Elastic Algorithm Service (EAS) page, find the deployed LLM intelligent router service.

  2. Click the service name to open the Overview page. In the Basic Informationsection, click View Endpoint Information .

  3. On the Invocation Method page, copy the Internet Endpoint and Token from the Service-specific Traffic Entry section.

    Important

    The service token is the administrator API key. If the administrator has assigned individual API keys to users through the WebUI console, each user should use their own API key to replace <YOUR_TOKEN> in the following example.

We recommend that you set them as environment variables for subsequent calls. Example commands:

export YOUR_GATEWAY_URL="https://*********3.cn-hangzhou.pai-eas.aliyuncs.com/api/predict/group_****y.ll****_gateway"
export YOUR_TOKEN="NzY4NWZ*************ZWU5Nw=="

Run a batch job

All examples use $YOUR_GATEWAY_URL and $YOUR_TOKEN from the environment variables set above.

1. Prepare the input file

Create input.jsonl. Each line is a self-contained JSON object representing one inference request.

{"custom_id": "request-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "Qwen3-8B", "messages": [{"role": "user", "content": "Hello world!"}]}}
{"custom_id": "request-2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "Qwen3-8B", "messages": [{"role": "user", "content": "Tell me a joke."}]}}

2. Upload the input file

curl -s "$YOUR_GATEWAY_URL/v1/files" \
  -H "Authorization: Bearer $YOUR_TOKEN" \
  -F purpose="batch" \
  -F file="@input.jsonl"

Sample response:

{
  "bytes": 564813,
  "created_at": 1765868482,
  "filename": "input.jsonl",
  "id": "batch_input_11fb297e-653d-47cf-bb6a-a80209dc562b",
  "object": "file",
  "purpose": "batch"
}

The file is stored at oss://my-batch-bucket/batchllm/batch_input_11fb297e-653d-47cf-bb6a-a80209dc562b.

3. Create a batch job

Replace <input_file_id> with the id from the upload response.

curl -s "$YOUR_GATEWAY_URL/v1/batches" \
  -H "Authorization: Bearer $YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "input_file_id": "<input_file_id>",
    "endpoint": "/v1/chat/completions",
    "completion_window": "24h"
  }'

Sample response:

{
  "id": "batch_5f968571-b0b6-413f-a2a8-69bf750112af",
  "object": "batch",
  "endpoint": "/v1/chat/completions",
  "input_file_id": "batch_input_11fb297e-653d-47cf-bb6a-a80209dc562b",
  "completion_window": "24h",
  "status": "pending",
  "output_file_id": null,
  "created_at": 1765868672,
  "expires_at": 1765955072,
  ...
}

4. Poll for job status

Replace {batch_id} with the id from the create response.

curl -s "$YOUR_GATEWAY_URL/v1/batches/{batch_id}" \
  -H "Authorization: Bearer $YOUR_TOKEN" \
  -H "Content-Type: application/json"

A job progresses through statuses in this order:

status: validating
status: in_progress
status: in_progress
status: finalizing
status: completed

A completed job returns:

{
  "id": "batch_5f968571-b0b6-413f-a2a8-69bf750112af",
  "status": "completed",
  "output_file_id": "batch_output_20bafd19-6d73-4b61-a770-ebcb377d286d",
  "request_counts": {
    "total": 2160,
    "completed": 2160,
    "failed": 0
  },
  ...
}

If status is failed, check the errors field for validation error details.

5. Retrieve the results

Replace {output_file_id} with the output_file_id from the status response.

curl -s "$YOUR_GATEWAY_URL/v1/files/{output_file_id}/content" \
  -H "Authorization: Bearer $YOUR_TOKEN" > output.jsonl

The output is a JSONL file. Output order is not guaranteed to match the input. Use custom_id to match each output line to its input request.

{"id":"batch_xxx","custom_id":"request-1","response":{"status_code":200,"request_id":"req_id_1","body":{...}}}
{"id":"batch_xxx","custom_id":"request-2","response":{"status_code":200,"request_id":"req_id_2","body":{...}}}

If a line contains an error field instead of response, that individual request failed. Collect failed requests by custom_id and resubmit them as a new batch job.

Run a batch job with a script

Save the following script as run_batch.sh. Replace <YOUR_GATEWAY_URL> and <YOUR_TOKEN> with your values.

#!/bin/bash

GATEWAY_URL="<YOUR_GATEWAY_URL>"
TOKEN="<YOUR_TOKEN>"

# 1. Upload the input file
echo "Uploading input file..."
UPLOAD_RESPONSE=$(curl -s "${GATEWAY_URL}/v1/files" \
  -H "Authorization: Bearer ${TOKEN}" \
  -F purpose="batch" \
  -F file="@input.jsonl")

INPUT_FILE_ID=$(echo ${UPLOAD_RESPONSE} | grep -o '"id": *"[^"]*"' | cut -d'"' -f4)
if [ -z "$INPUT_FILE_ID" ]; then
  echo "Failed to upload file. Response: $UPLOAD_RESPONSE"
  exit 1
fi
echo "Input file uploaded. File ID: ${INPUT_FILE_ID}"

# 2. Create the batch job
echo "Creating batch job..."
CREATE_RESPONSE=$(curl -s -X POST "${GATEWAY_URL}/v1/batches" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{
    \"input_file_id\": \"${INPUT_FILE_ID}\",
    \"endpoint\": \"/v1/chat/completions\",
    \"completion_window\": \"24h\"
  }")

BATCH_ID=$(echo ${CREATE_RESPONSE} | grep -o '"id": *"[^"]*"' | cut -d'"' -f4)
if [ -z "$BATCH_ID" ]; then
  echo "Failed to create batch job. Response: $CREATE_RESPONSE"
  exit 1
fi
echo "Batch job created. Batch ID: ${BATCH_ID}"

# 3. Poll the job status until it is complete
echo "Polling batch status..."
while true; do
  STATUS_RESPONSE=$(curl -s "${GATEWAY_URL}/v1/batches/${BATCH_ID}" \
    -H "Authorization: Bearer ${TOKEN}")

  STATUS=$(echo ${STATUS_RESPONSE} | grep -o '"status": *"[^"]*"' | cut -d'"' -f4)
  echo "Current status: ${STATUS}"

  if [[ "$STATUS" == "completed" ]]; then
    OUTPUT_FILE_ID=$(echo ${STATUS_RESPONSE} | grep -o '"output_file_id": *"[^"]*"' | cut -d'"' -f4)
    echo "Batch job completed. Output file ID: ${OUTPUT_FILE_ID}"
    break
  elif [[ "$STATUS" == "failed" ]]; then
    echo "Batch job failed. Check the errors field: ${STATUS_RESPONSE}"
    exit 1
  elif [[ "$STATUS" == "expired" || "$STATUS" == "cancelled" ]]; then
    echo "Batch job ended with status: ${STATUS}."
    exit 1
  fi
  sleep 10
done

# 4. Download the output file
echo "Downloading output file..."
curl -s "${GATEWAY_URL}/v1/files/${OUTPUT_FILE_ID}/content" \
  -H "Authorization: Bearer ${TOKEN}" > output.jsonl

echo "Results saved to output.jsonl."
cat output.jsonl

Run the script:

bash run_batch.sh

Batch job statuses

Status

Stage

Description

Can cancel

pending

Preparation

Job created, waiting to be picked up.

Yes

validating

Input file format and parameters are being validated. If validation fails, the job moves to failed.

Yes

in_progress

Processing

Requests are being sent to the backend inference service.

Yes

finalizing

Completion

All shards processed; system is waiting to aggregate results.

No

finalize

System is aggregating results and generating the output file.

No

completed

Job succeeded. The output file is available.

No

failed

Job failed during validation. Not processed.

No

cancelling

Cancellation received; in-progress requests are stopping.

No

cancelled

Job successfully canceled.

No

expired

Job did not complete within completion_window and was terminated.

No

API reference

The Batch API is compatible with the OpenAI Batch API.

Batch API

Batch object

Field

Type

Description

id

string

Unique identifier for the batch job.

object

string

Always batch.

endpoint

string

API endpoint called by the batch job (for example, /v1/chat/completions).

model

string

Model used in the batch request. Currently empty.

errors

object

Error details when status is failed.

errors.data

array

Validation error messages.

errors.data[].code

string

Error code.

errors.data[].line

int

Not supported. Always 0.

errors.data[].message

string

Error message.

errors.data[].param

string

Not supported. Always empty.

input_file_id

string

ID of the JSONL input file.

completion_window

string

Time window for job completion (for example, 24h).

status

string

Current job status.

output_file_id

string

ID of the output file. Present only when the job completes successfully.

error_file_id

string

ID of the file containing failed requests.

created_at

integer

Job creation time (UNIX timestamp, seconds).

in_progress_at

integer

Time when processing started (UNIX timestamp, seconds).

expires_at

integer

Expiration time. Jobs not completed by this time are automatically terminated (UNIX timestamp, seconds).

finalizing_at

integer

Time when the job entered the finalizing stage (UNIX timestamp, seconds).

completed_at

integer

Time when the job completed (UNIX timestamp, seconds).

failed_at

integer

Time when the job failed (UNIX timestamp, seconds).

expired_at

integer

Time when the job expired (UNIX timestamp, seconds).

cancelling_at

integer

Time when cancellation started (UNIX timestamp, seconds).

cancelled_at

integer

Time when the job was canceled (UNIX timestamp, seconds).

request_counts

object

Request statistics.

request_counts.total

integer

Total requests.

request_counts.completed

integer

Successful requests.

request_counts.failed

integer

Failed requests.

usage

object

Not supported.

metadata

map

Optional key-value metadata.

Create a batch job: POST /v1/batches

Request

curl -s "<YOUR_GATEWAY_URL>/v1/batches" \
  -H "Authorization: Bearer <YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "input_file_id": "batch_input_11fb297e-653d-47cf-bb6a-a80209dc562b",
    "endpoint": "/v1/chat/completions",
    "completion_window": "24h"
  }'

Request parameters

Parameter

Type

Required

Description

input_file_id

string

Yes

ID of the uploaded JSONL file. Maximum 50,000 requests; maximum 200 MB.

endpoint

string

Yes

API endpoint for the batch request.

completion_window

string

No

Time window for completion. Only 24h is supported.

metadata

map

No

Up to 16 key-value pairs. Keys up to 16 characters; values up to 512 characters.

output_expires_after

object

No

Not supported. Files do not expire automatically.

Response

Returns the created batch object.

Get batch status: GET /v1/batches/{batch_id}

Request

curl -s "<YOUR_GATEWAY_URL>/v1/batches/batch_98d4d6e3-c7ec-4aa9-969e-fb8531059523" \
  -H "Authorization: Bearer <YOUR_TOKEN>" \
  -H "Content-Type: application/json"

Request parameters

Parameter

Type

Required

Description

batch_id

string

Yes

ID of the batch job.

Response

Returns the batch object.

Cancel a batch job: POST /v1/batches/{batch_id}/cancel

Only jobs in validating or in_progress status can be canceled.

Request

curl -s -X POST "<YOUR_GATEWAY_URL>/v1/batches/batch_98d4d6e3-c7ec-4aa9-969e-fb8531059523/cancel" \
  -H "Authorization: Bearer <YOUR_TOKEN>"

Request parameters

Parameter

Type

Required

Description

batch_id

string

Yes

ID of the batch job to cancel.

Response

Returns the updated batch object with status: cancelling.

List batch jobs: GET /v1/batches

Request

curl -s "<YOUR_GATEWAY_URL>/v1/batches" \
  -H "Authorization: Bearer <YOUR_TOKEN>"

Request parameters

Parameter

Type

Required

Description

limit

integer

No

Maximum number of results to return.

after

string

No

Cursor for pagination. Pass the last_id from the previous response.

Response parameters

Parameter

Type

Description

object

string

Always list.

data

array

Batch objects sorted in reverse chronological order (newest first).

first_id

string

First batch_id in this response.

last_id

string

Last batch_id in this response.

has_more

bool

Whether more results exist after last_id.

File API

File object

Field

Type

Description

id

string

Unique file identifier.

object

string

Always file.

bytes

integer

File size in bytes.

created_at

integer

File creation time (UNIX timestamp, seconds).

expires_at

integer

File expiration time (UNIX timestamp, seconds).

filename

string

Filename specified at upload.

purpose

string

Always batch.

Input file format (JSONL)

One JSON object per line. Each object must include:

Field

Type

Required

Description

custom_id

string

Yes

Unique request identifier. Used to match output to input.

method

string

Yes

HTTP method. Use POST.

url

string

Yes

Inference endpoint. Must match the endpoint specified when creating the batch job.

body

object

Yes

Request body sent to the inference service.

Output file format (JSONL)

One JSON object per line. Output order is not guaranteed to match input.

Field

Type

Description

id

string

Batch job ID.

custom_id

string

Custom request ID from the input file.

response

object

Inference response. Present on success.

response.status_code

int

HTTP status code from the inference service.

response.request_id

string

Inference request ID.

response.body

object

Response body from the inference service.

error

object

Error information. Present when the request failed.

error.code

string

Error code.

error.message

string

Error message.

Upload a file: POST /v1/files

Request

curl "<YOUR_GATEWAY_URL>/v1/files" \
  -H "Authorization: Bearer <YOUR_TOKEN>" \
  -F purpose="batch" \
  -F file="@input.jsonl"

Request parameters

Parameter

Type

Required

Description

file

File

Yes

JSONL file to upload (multipart/form-data).

purpose

string

Yes

Must be batch (multipart/form-data).

Response

Returns the created file object.

List files: GET /v1/files

Request

curl "<YOUR_GATEWAY_URL>/v1/files" \
  -H "Authorization: Bearer <YOUR_TOKEN>"

Response parameters

Parameter

Type

Description

object

string

Always list.

data

array

File objects sorted in reverse chronological order (newest first).

first_id

string

First file_id in this response.

last_id

string

Last file_id in this response.

Get file metadata: GET /v1/files/{file_id}

Request

curl -s "<YOUR_GATEWAY_URL>/v1/files/batch_input_11fb297e-653d-47cf-bb6a-a80209dc562b" \
  -H "Authorization: Bearer <YOUR_TOKEN>"

Request parameters

Parameter

Type

Required

Description

file_id

string

Yes

ID of the file to query.

Response

Returns the file object.

Delete a file: DELETE /v1/files/{file_id}

Warning

Deleting a file removes only the metadata record in EAS. It does not delete the physical file from your OSS bucket. OSS files accumulate storage charges until you delete them manually or through lifecycle rules.

Request

curl -s -X DELETE "<YOUR_GATEWAY_URL>/v1/files/batch_input_11fb297e-653d-47cf-bb6a-a80209dc562b" \
  -H "Authorization: Bearer <YOUR_TOKEN>"

Request parameters

Parameter

Type

Required

Description

file_id

string

Yes

ID of the file to delete.

Response parameters

Parameter

Type

Description

id

string

File ID.

object

string

Always file.

deleted

bool

true

Get file content: GET /v1/files/{file_id}/content

Request

curl -s "<YOUR_GATEWAY_URL>/v1/files/batch_input_11fb297e-653d-47cf-bb6a-a80209dc562b/content" \
  -H "Authorization: Bearer <YOUR_TOKEN>"

Request parameters

Parameter

Type

Required

Description

file_id

string

Yes

ID of the file.

Response

Returns the file content as JSONL.

Best practices

Performance tuning

Tune batch_options based on your workload:

  • `--batch-parallel`: Start at instance * 2 and adjust based on GPU/CPU load on the backend inference service.

  • `--batch-lines-per-shard`: Set so that the total number of shards (input lines divided by lines per shard, rounded up) is an integer multiple of --batch-parallel. This maximizes GPU utilization. The recommended range is 500–2,000.

Cost management

  • Configure OSS lifecycle rules: Set expiration rules on the bucket or prefix used for batch files. This is the most effective way to prevent runaway storage costs from accumulating input, output, and error files.

  • Schedule jobs during off-peak hours: Running compute-intensive batch jobs at night or during low-traffic periods maximizes idle GPU utilization and reduces per-inference cost.

Job management

  • Split large jobs: For jobs with millions of requests, split them into multiple smaller batch jobs. Smaller jobs isolate failures, simplify retries, and are easier to manage.

FAQ

A job is stuck in pending or validating

Check three things in order: whether EAS service instances are running and have sufficient CPU, memory, and GPU; whether the input file was successfully uploaded to the correct OSS path; and whether the RAM role has oss:GetObject and oss:ListObjects permissions on the batch_oss_path.

A job has failed status

Jobs fail during validation, not execution. Call GET /v1/batches/{batch_id} and inspect the errors field. Common causes are an invalid JSONL format, a missing required field such as custom_id, or a url value that doesn't match the endpoint used when creating the job.

The job completed, but some requests failed

Download the output file using output_file_id. Filter for lines that contain an error field—these are the failed requests. Collect them by custom_id, add them to a new input file, and submit a new batch job to retry.

Diagnosing OSS permission issues

  • If the job fails during validating with a file-reading error, the RAM role is likely missing oss:GetObject.

  • If the job stays in finalizing or finalize for a long time and eventually expires, the RAM role is likely missing oss:PutObject for writing the output file to OSS.