一键导入
precision-align
Debug precision regressions in lmdeploy+dlinfer on domestic AI hardware (Ascend / CAMB / MACA) by comparing against a reference implementation.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Debug precision regressions in lmdeploy+dlinfer on domestic AI hardware (Ascend / CAMB / MACA) by comparing against a reference implementation.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Pull and analyze inference timelines on Ascend with lmdeploy+dlinfer. Covers profiling script templates, delay selection, raw timeline handling, fixed-real-batch decode/KV-context audit, and analysis reporting.
Start, monitor, and stop lmdeploy REST API services for any supported device/backend. Use when Codex needs to run `lmdeploy serve api_server` for a model path, choose model names from the model being served, set backend/device/max-batch/cache and user-provided parallel strategy flags, run outside the sandbox when devices are unavailable inside it, preserve server logs, check readiness, handle port conflicts, and cleanly stop the service before another run.
Run RESTful pressure benchmarks with lmdeploy's profile_restful_api.py after starting an lmdeploy API service. Use when Codex needs to choose ShareGPT native-length or random-length workloads, set tokenizer/model/client parameters without hard-coding qwen, compare models or max-batch settings, preserve logs, inspect running batch and KV cache behavior, and report throughput and cache/OOM handling.
The operator call chain in lmdeploy+dlinfer — the five layers every op crosses from model.forward() down to torch_npu/aclnn kernels, and how to trace or add a new op. Uses fused MoE (Qwen3.5 MoE) on Ascend as the worked example.
Understand the complete graph mode flow in lmdeploy+dlinfer, covering the runner architecture, buffer management, vendor differences, and common pitfalls.
Add support for a new model (already in lmdeploy's CUDA backend) on domestic AI hardware (Ascend / CAMB / MACA) via dlinfer.
| name | precision-align |
| description | Debug precision regressions in lmdeploy+dlinfer on domestic AI hardware (Ascend / CAMB / MACA) by comparing against a reference implementation. |
You are helping the user fix a precision bug in lmdeploy+dlinfer on a domestic AI hardware backend (Ascend, CAMB, or MACA). The reference implementation is typically vllm+vllm-ascend (for Ascend) or another agreed-upon reference. The goal is to identify where lmdeploy+dlinfer diverges from the reference and fix it.
The examples in this skill use Ascend as the concrete hardware. Apply the same methodology to CAMB and MACA by substituting the appropriate vendor paths and ops.
Ask the user:
qwen3, deepseek_v2)Do not proceed until these are answered.
Before any debugging, confirm the comparison environment is controlled. Both sides must be identical except for the framework under test:
| Condition | lmdeploy+dlinfer | vllm+vllm-ascend |
|---|---|---|
| Same SoC version | ✓ | ✓ |
| Warmup disabled | ✓ | ✓ |
| Eager mode | ✓ (--eager-mode true) | ✓ (--enforce-eager) |
| Same TP / DP / EP | ✓ | ✓ |
temperature=0,top_k=1 | ✓ | ✓ |
| Same prompt / input | ✓ | ✓ |
If any condition is unmet, fix it first. Warmup leaves stale KV cache entries; temperature > 0 introduces sampling randomness — both mask real precision bugs.
Run both frameworks on the same prompt and compare the generated tokens directly.
Map the symptom to a debugging path:
| Symptom | Most likely cause | Path |
|---|---|---|
| First token already wrong | Prefill operator precision | B |
| First token correct, then diverges | KV cache or decode op | A or B |
| Divergence grows with seq length | KV cache or op precision | A or B |
| Divergence at a fixed depth | Operator precision | B |
| Only wrong at TP > 1 / dp×tp / ep | Communication / parallelism | C |
| Only wrong with multiple requests | Batching / seqlen / masking | A or B |
Important: accumulating divergence does not imply KV cache pollution. An operator precision bug can also compound over decode steps — for example, a rope embedding op silently falling back to CPU produces slightly wrong position encodings that accumulate into a visible accuracy drop (observed on Qwen30B-A3B: a 2-point drop on LiveCodeBench traced to cos/sin computed on CPU). Do not assume Path A without ruling out Path B first.
If unsure:
KV cache pollution means fill_kv_cache was called with mismatched indices,
writing tokens into wrong cache slots (stomping). The fill_kv_cache kernel
itself is generally not the source of bugs — the problem is almost always in
the indices passed to it.
Read these two files:
lmdeploy/lmdeploy/pytorch/kernels/dlinfer/fill_kv_cache.pylmdeploy/lmdeploy/pytorch/kernels/dlinfer/pagedattention.pyKey parameters:
kv_start_indices in fill_kv_cache: flat index of each token's cache
slot.block_offsets, q_start_loc, q_seq_len, kv_seq_len in
prefill_attention and paged_token_attention / paged_attention_fwd.Multi-batch note: if the bug only appears with multiple requests, pay extra
attention to per-request seqlen tracking (q_seq_len, kv_seq_len) and
kv_start_indices. A wrong per-request length causes attention to read from
the wrong positions in the KV cache.
Do not dump the KV cache tensors — they are prohibitively large. Instead,
dump the three tensors immediately before the fill_kv_cache call at the
suspect layer:
dump("key_states", key_states) # [num_tokens, num_kv_heads, head_size]
dump("value_states", value_states) # [num_tokens, num_kv_heads, head_size]
dump("kv_start_indices", kv_start_indices) # shape: [num_tokens]
Key check: kv_start_indices.shape[0] must equal key_states.shape[0].
A mismatch means the index count does not match the token count being written,
which causes fill-time stomping and corrupts subsequent decode steps.
The goal is to find the first op where lmdeploy+dlinfer diverges from the reference.
Single-batch first: if the issue is reproducible with a single request, debug at batch_size=1. This eliminates batching interactions and simplifies seqlen shapes.
Start at layer 0 of the first linear-attention or full-attention block. Do not start at the midpoint: most of the model's layers share the same operator set, so layer 0's result is representative. If layer 0 is clean, most other layers will be too; if layer 0 already diverges, fix it before searching deeper.
For deterministic vendor ops (e.g. torch_npu on Ascend): use
torch.equal(). These ops must produce bit-identical outputs. Any difference
is a real bug.
For non-deterministic ops (e.g. triton, less common on Ascend):
torch.equal() may be too strict due to FP rounding. Check error magnitude
instead:
diff = (a - b).abs()
print("max abs:", diff.max().item())
print("max rel:", (diff / b.abs().clamp(min=1e-8)).max().item())
A relative error below ~1e-3 is generally acceptable; above that it is a real divergence.
Read its implementation stack:
lmdeploy/lmdeploy/pytorch/backends/dlinfer/<vendor>/ — the Impl classlmdeploy/lmdeploy/pytorch/kernels/dlinfer/ — the thin kernel wrapperdlinfer/dlinfer/vendor/<vendor>/ — the actual hardware op call
(e.g. for Ascend: ascend/torch_npu_ops.py)Dump the inputs to that op in both frameworks and check whether they are identical. If inputs differ, the bug is upstream; if inputs are identical but outputs differ, the bug is in the op itself (wrong argument order, dtype, or shape).
Precision bugs that only appear with certain parallelism configurations point to communication or parallelism-patch issues. Before debugging, understand which parallelism level introduces the problem.
lmdeploy supports three parallelism dimensions used in combination:
tp
GPUs. Total GPUs = tp.tp_size = tp / dp. When EP=1, tp in the config equals the total
GPU count.ep groups. When EP>1, tp in the config is the
tp_size per DP group (not the total GPU count).Not all models fit at TP=1. Work through the parallelism hierarchy from simplest to most complex, stopping at the level that introduces the bug:
TP only (simplest that fits the weights): run both frameworks with TP=N, DP=1, EP=1 (where N is the minimum number of GPUs needed to load the model).
dp×tp (add DP): increase DP while keeping EP=1.
dlinfer/dlinfer/framework/lmdeploy_ext/device/<vendor>.py for relevant
patches.dp×tp + ep (add EP): enable EP>1.
device/<vendor>.py (e.g.
AscendMoEForwardDPTP in device/ascend.py) and verify the MoE routing
and reduce-scatter pattern.When dp > 1, lmdeploy fills DP groups that have no real requests with dummy data of sequence length 1. vllm-ascend uses a similar mechanism. This is expected behaviour, not a bug. When dumping tensors across DP groups:
If the model is too large to fit at TP=1, start at the minimum TP that loads the weights and compare it against the same TP on the reference side. You can still isolate DP and EP by fixing TP and varying DP/EP independently.
dlinfer/dlinfer/framework/lmdeploy_ext/device/<vendor>.py — patches for
distributed behaviours specific to the hardware (e.g. for Ascend: ascend.py
containing AscendMoEForwardDPTP for MoE communication).Always dump to files. Never use print or logger.
On multi-rank runs, log output from all ranks interleaves and individual
tensor values are lost. Use torch.save to per-rank files instead.
import os, torch, torch.distributed as dist
_DUMP_DIR = "/tmp/dlinfer_dump"
os.makedirs(_DUMP_DIR, exist_ok=True)
def dump(name: str, tensor: torch.Tensor):
rank = dist.get_rank() if dist.is_initialized() else 0
torch.save(tensor.detach().cpu(), f"{_DUMP_DIR}/{name}_rank{rank}.pt")
Naming convention: {layer}_{op}_{input|output}_rank{rank}.pt
Example: layer0_attn_out_rank0.pt for lmdeploy+dlinfer, same name in a
separate directory for vllm+vllm-ascend, so files are easy to pair.
Loading and comparing:
a = torch.load("dlinfer/layer0_attn_out_rank0.pt")
b = torch.load("vllm/layer0_attn_out_rank0.pt")
# deterministic vendor ops (e.g. torch_npu on Ascend) — expect exact match
print(torch.equal(a, b))
# triton / float ops — check error magnitude
diff = (a - b).abs()
print("max abs:", diff.max().item())
print("max rel:", (diff / b.abs().clamp(min=1e-8)).max().item())
Placement of dump calls: add dumps inside the
lmdeploy/lmdeploy/pytorch/backends/dlinfer/<vendor>/ Impl classes — right
after the kernel call and before returning. This gives the output in
framework-native shape and is above the vendor-specific layer.
--eager-mode true, same TP/DP/EP,
temperature=0 / top_k=1 confirmedSymptom: kv_start_indices length matches key_states, but divergence
still grows with sequence length.
Cause: Operator precision bugs (e.g. cos/sin falling back to CPU) also accumulate across decode steps and look identical to cache pollution from the outside.
Action: Shift to Path B. Dump layer 0 sub-ops (RMSNorm → Attention → MLP) to confirm whether divergence begins there.
Symptom: When dp > 1, tensors in idle DP groups have a leading dimension of 1 and produce odd-looking outputs.
Cause: lmdeploy (and vllm-ascend) fill DP groups that have no real requests with dummy data of sequence length 1. This is expected behaviour.
Action: Only compare tensor values in DP groups that are actually processing real tokens. Do not treat length-1 tensors from idle groups as errors.
Symptom: Adding DP or EP introduces a precision regression, but the root cause dimension is unknown.
Cause: Testing multiple parallelism dimensions simultaneously makes it impossible to isolate which one introduces the bug.
Action: Fix TP, add DP first. If dp×tp is clean, then add EP. See Path C for the full isolation strategy.
Symptom: Saved dump files have no usable data, or values from multiple ranks are mixed together.
Cause: Using print or logger on multi-rank runs causes output from all
ranks to interleave and overwrite each other.
Action: Use torch.save to write a separate file per rank. See the Tensor
dump section for the pattern.
Symptom: Same prompt gives different results across runs; the precision issue is not consistently reproducible.
Cause: Warmup leaves stale KV cache entries that corrupt subsequent inference runs.
Action: Disable warmup on both lmdeploy+dlinfer and vllm+vllm-ascend, then retry.
Symptom: Token-level comparison is not stable; the outputs change each run.
Cause: temperature > 0 introduces sampling randomness that makes
comparison meaningless.
Action: Set temperature=0, top_k=1 on both sides.
Symptom: After several bisection steps the divergent layer turns out to be very early in the model.
Cause: Starting at layer N/2 skips checking whether layer 0 is already wrong.
Action: Always verify layer 0 first. If layer 0 is clean, then apply binary search to the remaining layers.
Symptom: Inputs to the op match between frameworks, but you cannot tell whether the op itself is at fault.
Cause: Without output dumps, there is no evidence of whether the op produces wrong results.
Action: Dump both inputs and outputs. If inputs match but outputs differ, the bug is in the op call itself — check argument order, dtype, and shape passed to the hardware op (e.g. NPU op on Ascend).