Qwen webhook timeout after 30 seconds in Docker under CPU limit

Qwen Webhook Timeout After 30 Seconds in Docker Under CPU Limit

Problem

When the Qwen inference service runs inside a Docker container on a Linux host, HTTP requests sent to the /v1/completions webhook are terminated after exactly 30 seconds, even though the model can complete the inference in 45‑60 seconds under normal load. The client receives a 504 Gateway Timeout (or Docker’s client timeout) and the service logs show a generic “request timed out” message.

Typical symptom log excerpt:


2024-10-16T12:34:56.789Z ERROR webhook_handler.py:112 request_id=7f3c2a timeout after 30s
2024-10-16T12:34:56.790Z INFO  docker: container qwen-infer exited with code 137

The failure is reproducible with any payload that requires more than 30 seconds of CPU time, but short prompts (< 10 seconds) succeed.

Root Cause

The container is started with a CPU quota that limits the process to 0.5 CPU cores (--cpus=0.5 or cpu_quota/cpu_period cgroup settings). Qwen’s transformer inference is CPU‑bound; when the quota is hit, the Linux scheduler throttles the process, causing the request handling thread to be blocked for the remainder of the 30‑second HTTP server timeout.

Docker’s default --stop-timeout and the webhook server’s own request timeout are both set to 30 seconds. Because the container never reaches the point where it writes a response, the HTTP server aborts the connection, and Docker reports a timeout.

Debug / Investigation Steps

  1. Confirm CPU limits:

    docker inspect qwen-infer --format='{{json .HostConfig.CpuQuota}} {{json .HostConfig.CpuPeriod}}'

    Typical output:

    50000 100000

    This translates to 0.5 CPU.

  2. Measure actual CPU throttling using cgroup stats:

    cat /sys/fs/cgroup/cpu/docker/$(docker inspect -f '{{.Id}}' qwen-infer)/cpu.stat

    Sample output when the timeout occurs:

    nr_periods 1200
    nr_throttled 1185
    throttled_time 11520000000

    High nr_throttled indicates the process is being throttled for most of the time.

  3. Reproduce without CPU limit:

    docker run -d --name qwen-unlimited \\
      -p 8080:8080 \\
      myrepo/qwen:latest

    Running the same request now completes in ~55 seconds, confirming the limit is the culprit.

  4. Check the webhook server timeout configuration (example for uvicorn):

    cat /app/config.yaml | grep timeout
    # timeout: 30
  5. Inspect host‑level Docker daemon timeout (unlikely but verify):

    cat /etc/docker/daemon.json | grep -i timeout
    # {} (no custom timeout)

Solution

Two complementary changes resolve the issue:

1. Adjust container CPU allocation

Allocate at least one full CPU core to the inference container, or use a higher quota proportionate to the expected latency.

# Before
docker run -d --name qwen-infer \\
  --cpus=0.5 \\
  -p 8080:8080 \\
  myrepo/qwen:latest

# After (allocate 2 CPUs)
docker run -d --name qwen-infer \\
  --cpus=2 \\
  -p 8080:8080 \\
  myrepo/qwen:latest

Alternatively, remove the limit entirely if the host has spare capacity:

docker run -d --name qwen-infer -p 8080:8080 myrepo/qwen:latest

2. Increase the webhook request timeout

Modify the server configuration to allow longer processing times. For a FastAPI/Uvicorn deployment:

# config.yaml (before)
timeout: 30

# config.yaml (after)
timeout: 120   # generous buffer for heavy prompts

Restart the container after updating the config.

Why the fix works

  • Providing sufficient CPU eliminates throttling, allowing the inference engine to progress without being paused by the scheduler.
  • Extending the HTTP timeout gives the server enough wall‑clock time to finish the computation and send the response before the connection is closed.

Verification

After applying the changes, run a payload that previously timed out:

curl -X POST http://localhost:8080/v1/completions \\
  -H "Content-Type: application/json" \\
  -d '{"model":"qwen","prompt":"Explain quantum tunneling in detail."}'

Expected output (truncated):

{
  "id":"cmpl-12345",
  "object":"text_completion",
  "created":1729104000,
  "model":"qwen",
  "choices":[{"text":"..."}],
  "usage":{"prompt_tokens":12,"completion_tokens":210,"total_tokens":222}
}

Confirm no timeout logs appear:


2024-10-16T12:40:12.345Z INFO  webhook_handler.py:98 request_id=7f3c2a completed in 52.3s

Also verify cgroup throttling stats have dropped:

cat /sys/fs/cgroup/cpu/docker/$(docker inspect -f '{{.Id}}' qwen-infer)/cpu.stat
nr_periods 1200
nr_throttled 0
throttled_time 0

Prevention / Best Practices

  • Capacity planning: Benchmark the model on representative hardware and set --cpus to at least the measured average CPU usage plus a safety margin (e.g., 1.5×).
  • Dynamic timeout: Expose a configurable timeout parameter and adjust per request size (e.g., longer for >512‑token prompts).
  • Monitoring: Export cpu.throttled.time and cpu.throttled.periods metrics to Prometheus; alert when throttling exceeds 10 % over a 5‑minute window.
  • Graceful degradation: Implement request queueing or model off‑loading when CPU usage exceeds a threshold, returning a 429 Too Many Requests instead of a hard timeout.
  • Container health checks: Use a lightweight health endpoint that returns 200 only when the process is not throttled (e.g., check /proc/self/status for CpuQuota).

FAQ

  1. Why does the timeout happen exactly at 30 seconds?
    Because the webhook server’s timeout setting defaults to 30 seconds and Docker’s default request timeout aligns with that value. When the process is throttled, it never reaches the response stage, so the server aborts at the configured limit.
  2. Can I keep the CPU limit and still avoid timeouts?
    Only if you also lower the model’s computational load (e.g., use a smaller quantized variant) or increase the timeout proportionally. However, the inference latency will still be bound by the limited CPU, so the practical solution is to raise the CPU allocation.
  3. Is there a way to detect throttling from inside the container?
    Yes. Read /sys/fs/cgroup/cpu/cpu.stat and watch nr_throttled and throttled_time. A non‑zero value indicates the scheduler is limiting CPU usage.
  4. Do I need to adjust the host kernel’s CFS scheduler settings?
    Usually not. Docker’s cgroup limits are sufficient. Changing host‑level scheduler parameters can have unintended side effects on other workloads.
  5. What if the service must run on a shared node with strict CPU caps?
    Consider deploying a separate inference node with dedicated resources, or use a GPU‑accelerated version of Qwen that is less CPU‑bound, thereby reducing the need for high CPU quotas.

Related Topic Hub: LLM Systems Troubleshooting Hub