Serving

July 5, 2026 ยท View on GitHub

Modes

Colocated

Scheduler + workers run in colocated mode, with each worker performing both prefill and decode.

uv run python -m sangam.entrypoints.launch \
    --model GSAI-ML/LLaDA-8B-Instruct \
    --mode colocated \
    --gpus 0

Hybrid (Conditional Disaggregation)

Dedicated prefill workers and colocated workers which initially only process decodes. When prefill workers are overloaded, --enable-hybrid-prefill-overflow lets prefills fall through to colocated workers for local prefill+decode.

uv run python -m sangam.entrypoints.launch \
    --model GSAI-ML/LLaDA-8B-Instruct \
    --mode hybrid \
    --prefill-gpus 0 \
    --hybrid-colocated-gpus 1 \

Overflow is off by default, so hybrid mode runs in a pure disaggregated manner unless you pass --enable-hybrid-prefill-overflow.

Invocation Notes

A list of all CLI arguments with helptexts can be found using the command:

uv run python -m sangam.entrypoints.launch --help

Key CLI Arguments

Core

ArgumentDefaultDescription
--modecolocatedServing mode: colocated or hybrid
--modelGSAI-ML/LLaDA-8B-InstructHuggingFace model name
--scheduler-port50051gRPC port for the scheduler
--gpus0,1Comma-separated GPU IDs for colocated workers
--prefill-gpus0,1Comma-separated GPU IDs for prefill workers (hybrid)
--hybrid-colocated-gpus1,2,3Comma-separated GPU IDs for colocated workers (hybrid)
--base-worker-port20100Starting port for worker gRPC servers
--master-addrlocalhosttorch.distributed master address
--master-port29500torch.distributed master port
--max-batch-size128Max decode steps to batch per forward pass
--block-length32Tokens per generation block for every request handled by this server
--max-gen-lenNoneFixed prompt+mask length; when set, must be a positive multiple of --block-length
--mask-idNoneToken ID for masked positions; falls back to the HF config mask_token_id
--colocated-sticky-workerfalsePin each request to its first-assigned worker

Scheduling & topology

ArgumentDefaultDescription
--max-tokens-per-iteration4096Colocated mode token budget per scheduler iteration (decode + optional prefill admission)
--max-prefill-tokens-per-batch4096Max total token count across all requests in a single prefill forward pass
--prefill-scheduler-policyleast_outstanding_prefill_tokensPrefill assignment policy: round_robin, least_outstanding_prefill_tokens, or colocated-only least_outstanding_requests, least_request_length_sum, balanced_length_clustering
--prefill-queue-policyarrival_orderPer-worker prefill queue ordering: arrival_order or fewest_remaining_blocks
--decode-scheduler-policymax_free_memoryDecode assignment policy: round_robin, max_free_memory, balanced_length_clustering, or topology-aware topology_guarded_memory in hybrid mode
--decode-grouping-slack-ratio0.10Slack ratio for decode balanced_length_clustering; workers within min_projected_sum * (1 + slack) remain eligible before clustering preference is applied
--kv-fast-pairs0-1,2-3,4-5,6-7Undirected fast GPU link pairs for topology-aware decode routing in hybrid mode, for example 0-1,2-3
--kv-topology-alpha0.0Prefer a fast-link decode worker only when its free KV pages are at least alpha * mem_best.free_pages
--enable-hybrid-prefill-overflow / --no-enable-hybrid-prefill-overflowon in hybrid modeHybrid-only: allow overloaded prefill workers to overflow to colocated workers for local prefill+decode. On by default in hybrid mode; pass --no-enable-hybrid-prefill-overflow to queue such requests as pending instead
--prefill-overload-threshold16384Hybrid-only: outstanding prefill tokens per worker above which requests overflow to colocated workers

KV cache

ArgumentDefaultDescription
--kv-page-size16Tokens per KV cache page
--kv-max-pagesauto (per model)Max KV cache pages per decode worker. Auto-selected from the model's HF architecture when omitted (Dream 49152, LLaDA 5632); pass an explicit value to override

CUDA graphs

ArgumentDefaultDescription
--enable-cuda-graphs / --no-enable-cuda-graphsonCapture and replay CUDA graphs for decode-only batches. On by default; pass --no-enable-cuda-graphs to disable
--cuda-graph-batch-sizes1,2,4,8,16,24,32,40,48,56,64Comma-separated decode batch sizes to capture, capped at --max-batch-size

Metrics

ArgumentDefaultDescription
--metrics-output-dirbenchmark_output/<timestamp>Directory for CSV and plot output
--disable-metricsfalseDisable metrics collection
--enable-individual-batch-metricsfalseEnable per-batch raw CSV export (worker_batch_metrics.csv)

Additional operation-level metrics flags (--enable-operation-metrics, --op-metrics-layer-id, --export-partial-metrics) are covered in the metrics doc.

Generation Parameters

ParameterDescription
gen_lengthTotal tokens to generate; must be divisible by the server --block-length
temperatureSampling temperature (0.0 = greedy via Gumbel max)
unmasking_strategyrandom, conf_threshold, conf_quota, or conf_dynamic
confidence_thresholdRequired when unmasking_strategy=conf_threshold
fixed_unmask_quotaRequired when unmasking_strategy=conf_quota
dynamic_unmask_factorRequired when unmasking_strategy=conf_dynamic
request_seedOptional stable sampling seed (a top-level request field, not part of sampling_parameters); if omitted, the scheduler derives a deterministic fallback from request contents and sampling parameters

Request Submission

Use scripts/submit_request.py as the minimal client. A request consists of:

  • prompt_token_ids: fully tokenized prompt payload
  • gen_length: number of completion tokens to generate; it must be divisible by the server --block-length
  • request_seed: optional top-level request field (not nested under sampling_parameters) carrying a per-request sampling seed; identical explicit seeds reproduce the same nonzero-temperature sampling across schedulers
  • sampling_parameters: nested sampling config carrying temperature, unmasking_strategy, and any strategy-specific optional fields

If request_seed is omitted, the scheduler hashes the request payload, sampling parameters, and server block length to derive a deterministic fallback seed before the first worker enqueue. That means duplicate implicit requests intentionally share randomness within a given server configuration.

Polling returns the full current sequence plus a string status. The scheduler currently uses these statuses:

  • PENDING
  • PREFILLING
  • WAITING_DECODE
  • DECODING
  • WAITING_NEXT_BLOCK
  • COMPLETED
  • ERROR
  • NOT_FOUND for an unknown request id

Treat COMPLETED and ERROR as terminal states.

Troubleshooting

If launch succeeds but requests do not complete, check these first:

  • Python/CUDA environment: this project requires Python 3.13, CUDA 12.8, and compatible GPU drivers
  • Dependency resolution: uv sync must be able to use the configured FlashInfer package source from pyproject.toml
  • Ports: ensure --scheduler-port and --base-worker-port are free and do not overlap with other local runs
  • Worker topology: hybrid mode needs both --prefill-gpus and --hybrid-colocated-gpus; colocated mode needs --gpus
  • Scheduler readiness: if workers have not registered yet, requests can remain pending
  • CUDA/NCCL issues: a worker process can start but fail to make progress if inter-GPU communication is misconfigured
  • Terminal status: inspect ERROR responses from Poll; NOT_FOUND means the request id is unknown to the scheduler