Skip to content

feat(export): write a merged model as a compressed-tensors checkpoint for vLLM - #5

Open
gapsong wants to merge 3 commits into
mainfrom
fm/qpeft-ct-export
Open

gapsong wants to merge 3 commits into
mainfrom
fm/qpeft-ct-export

Conversation

@gapsong

@gapsong gapsong commented Oct 12, 2026

Copy link
Copy Markdown
Owner

Intent

Add a compressed-tensors "pack-quantized" WNA16 asymmetric export (with zero-points, 2 to 8 bits) so vLLM can serve a merged qpeft model. Our GPTQ export cannot be served by vLLM, because vLLM's GPTQ path takes only symmetric 4/8 bit. Symmetric mode is deliberately NOT offered: it would throw away the trained zero-points, which are the point of qpeft.

Requirements from the user:

  • Share code with the GPTQ exporter (new private qpeft/export/_checkpoint.py), keep each exporter a deep module with one small public entry point (export_compressed_tensors / export_gptq). Refactor of gptq.py must be minimal and covered by its existing tests.
  • Field names, dtypes, packing and sign convention were verified in the compressed-tensors and vLLM loader source, not assumed: packed words hold qpeft's unsigned q and z unchanged (compressed-tensors stores v - 2**(bits-1) and packs v + 2**(bits-1)); weight_packed = qweight.t(); weight_zero_point packed along outputs; weight_shape int64; scales kept in the model dtype.
  • Refuse instead of approximate (project invariant 2): fractional zero-points (QA-LoRA, decision D2), quantized layers that the loader skeleton does not hold as a plain nn.Linear (e.g. MoE router subclass), weights tied to another tensor (tied lm_head), scales in a dtype other than the checkpoint dtype (no cast at all), bits outside 2..8.
  • Targets are written as full module names on purpose (compressed-tensors and vLLM match them exactly, including vLLM's fused qkv/gate_up shards). One config group per (bits, group_size).
  • 3/5/6/7 bits use the dense packing of compressed-tensors >= 0.18; 0.17 pads differently and there is no version flag in the checkpoint - this is documented, not detected.
  • Validation done: bitwise equivalence tests; a CPU load test with transformers + compressed-tensors (tests/test_compressed_tensors_load.py, skipped unless compressed-tensors>=0.18) giving bitwise-equal logits for PEQA and EfficientQAT, bits 2-8, bf16/fp16; vLLM 0.29 on a GB10 GPU served 4-bit (Marlin) and 8-bit (Humming) close to the exact model, and refused 2/3-bit asymmetric. Those results are recorded in README and AGENTS.md.
  • Shared test helpers moved from tests/test_gptq_export.py into tests/export_models.py; test models are built with _from_config(dtype=) because .to(dtype) rounds rotary inv_freq.
  • The branch was rebased onto main after PR 3 (GPTQ export) and PR 4 (axolotl plugin) merged; doc conflicts in README.md/AGENTS.md were resolved keeping both sides.
  • Do not merge the PR, do not push to forks, do not open upstream issues. CI is skipped for this run by the user's request.

What Changed

  • New qpeft.export.export_compressed_tensors(model, save_directory) writes a merged EfficientQAT or PEQA model as an asymmetric pack-quantized WNA16 checkpoint for 2 to 8 bits.
    Per layer it writes weight_packed (qweight.t()), weight_scale (kept in the model dtype), weight_zero_point (packed along the outputs) and weight_shape (int64).
    config.json gets one config group per (bits, group_size), and each group names its layers in full.
    The export refuses fractional zero-points (QA-LoRA), scales in a different dtype than the checkpoint, unsupported bits, and quantized layers that the loader's config-built model does not hold as a plain, untied nn.Linear (for example an MoE router or a tied lm_head).
  • Shared checkpoint code moves into the private qpeft/export/_checkpoint.py: finding merged layers, the integer zero-point check, the meta-device loader skeleton, and the save_pretrained + quantization_config write.
    export_gptq now uses it, and its behavior does not change, except that it now checks that every layer is merged before its other checks.
  • Tests: the shared model builders move from tests/test_gptq_export.py to tests/export_models.py, and the builders now use _from_config(dtype=...).
    New tests/test_compressed_tensors_export.py covers the bit-exact decode and every refusal.
    New tests/test_compressed_tensors_load.py does a real CPU load through transformers + compressed-tensors (skipped without compressed-tensors >= 0.18).
    README and AGENTS.md now document the format, the loader facts and the vLLM 0.29 results.

Risk Assessment

⚠️ Medium: The exporter is well-bounded and refuses inexact cases, with bitwise tests and a minimal GPTQ refactor. One likely gap remains: the empty ignore list may stop vLLM from serving partial-target or MoE exports (a loud failure, not a silent one).

Testing

In a throwaway venv (compressed-tensors 0.19, transformers 5.19, gptqmodel + optimum), I ran the targeted export and load tests: compressed-tensors export, compressed-tensors load, GPTQ export, and the real GPTQ load that covers the refactor. All passed. I then ran the export as an end user would on the real SmolLM2-135M model. PEQA and EfficientQAT exports at bits 2, 3, 4, 5, 7 and 8, in bf16 and fp16, plus a partial-target export, reload through transformers with logits equal bit for bit, and greedy text is the same. All five refusal guards fire and write no files. For vLLM I used disposable docker containers. The arm64 CPU build of vLLM 0.31 accepted the 4-bit export and loaded every weight with its CompressedTensorsWNA16 kernel, but it cannot run the forward: cpu_gemm_wna16 is not compiled for arm64, and the CPU path supports only 4-bit. The x86_64 build hits SIGILL under emulation (no AVX-512). So vLLM generation itself stays untested here. I removed the disposable venv contents, the pulled images and the worktree build files. This is not a UI change, so there are no screenshots; the evidence is CLI transcripts and logs.

  • Live validation: ✅ go - 5 of 6 scenarios driven live against the product
Scenario Result Live Evidence
User merges a PEQA / EfficientQAT model, calls export_compressed_tensors, and transformers + compressed-tensors reload it with logits equal bit for bit (real SmolLM2-135M, 3/4/8 bits, bf16 and fp16) ✅ pass live smollm2-export-reload-transcript.txt: logits bit-for-bit equal True, greedy generations equal True for all three runs; config.json shows quant_method compressed-tensors, pack-quantized, symmetric fals…
Dense packing at odd widths and the full 2..8 range: 2/5/7-bit real-model exports and bits 2-8 on tiny Llama (bf16/fp16, PEQA and EfficientQAT) reload bit for bit through transformers ✅ pass live smollm2-more-bits-transcript.txt; pytest tests/test_compressed_tensors_load.py in pytest-export-load.txt
Partial targets (q_proj, v_proj only) export reloads bit for bit through transformers ✅ pass live smollm2-more-bits-transcript.txt (60 layers, logits bit-for-bit equal True)
Adversarial: a tied lm_head, a QA-LoRA fractional zero-point, an unmerged model, an fp32 scale in a bf16 model, and bits=1 are each refused with UnsupportedSchemeError and no output directory is writt… ✅ pass live refusals-transcript.txt (all 5 refused; 'output dir exists afterwards: False'); refusal tests in pytest-export-load.txt (also the MoE-router subclass and a non-HF model)
GPTQ exporter after the refactor into _checkpoint.py still writes checkpoints that gptqmodel + optimum load bit for bit ✅ pass live pytest tests/test_gptq_load.py (pytest-gptq-load.txt, 18 passed) and pytest tests/test_gptq_export.py
vLLM serves the 4-bit compressed-tensors export and generates text close to the exact model ⏸️ untested no No CUDA GPU on this Apple Silicon host, and vLLM has no macOS wheel. I ran vLLM 0.31 from its official CPU docker images in disposable containers. The arm64 image parsed the export and loaded all weig…
Evidence: Targeted pytest run: compressed-tensors export/load + GPTQ export
........................................................................ [ 70%]
..............................                                           [100%]
102 passed in 6.56s
Evidence: Real GPTQ load through gptqmodel + optimum after the refactor
..................                                                       [100%]
18 passed in 18.99s
Evidence: SmolLM2-135M export -> transformers reload transcript (4-bit PEQA bf16, 3-bit EfficientQAT fp16, 8-bit PEQA bf16)

bits=4 peqa bf16: weight_packed (576,192) int32, weight_scale (576,24) bf16, weight_zero_point (72,24) int32, weight_shape int64; logits bit-for-bit equal: True; greedy 'The capital of France is Paris.\n\nThe French language is a very rich language' equal: True bits=3 efficient_qat fp16: logits bit-for-bit equal: True; greedy equal: True bits=8 peqa bf16: logits bit-for-bit equal: True; greedy equal: True

=== bits=4 method=peqa dtype=bfloat16 ===
files: ['config.json', 'generation_config.json', 'model.safetensors', 'tokenizer.json', 'tokenizer_config.json']
quant_method: compressed-tensors | format: pack-quantized | groups: ['group_0']
weights: {"actorder": null, "dynamic": false, "group_size": 64, "num_bits": 4, "strategy": "group", "symmetric": false, "type": "int"}
n targets: 210 first: ['model.layers.0.self_attn.q_proj', 'model.layers.0.self_attn.k_proj', 'model.layers.0.self_attn.v_proj']
  model.layers.0.mlp.down_proj.weight_packed: (576, 192) torch.int32
  model.layers.0.mlp.down_proj.weight_scale: (576, 24) torch.bfloat16
  model.layers.0.mlp.down_proj.weight_zero_point: (72, 24) torch.int32
  model.layers.0.mlp.down_proj.weight_shape: (2,) torch.int64
logits bit-for-bit equal (qpeft merged vs transformers-loaded export): True | max abs diff: 0.0
qpeft    : 'The capital of France is Paris.\n\nThe French language is a very rich language'
exported : 'The capital of France is Paris.\n\nThe French language is a very rich language'
greedy generations equal: True
=== bits=3 method=efficient_qat dtype=float16 ===
files: ['config.json', 'generation_config.json', 'model.safetensors', 'tokenizer.json', 'tokenizer_config.json']
quant_method: compressed-tensors | format: pack-quantized | groups: ['group_0']
weights: {"actorder": null, "dynamic": false, "group_size": 64, "num_bits": 3, "strategy": "group", "symmetric": false, "type": "int"}
n targets: 210 first: ['model.layers.0.self_attn.q_proj', 'model.layers.0.self_attn.k_proj', 'model.layers.0.self_attn.v_proj']
  model.layers.0.mlp.down_proj.weight_packed: (576, 144) torch.int32
  model.layers.0.mlp.down_proj.weight_scale: (576, 24) torch.float16
  model.layers.0.mlp.down_proj.weight_zero_point: (54, 24) torch.int32
  model.layers.0.mlp.down_proj.weight_shape: (2,) torch.int64
logits bit-for-bit equal (qpeft merged vs transformers-loaded export): True | max abs diff: 0.0
qpeft    : 'The capital of France is Paris. It is the capital of France. It is the'
exported : 'The capital of France is Paris. It is the capital of France. It is the'
greedy generations equal: True
=== bits=8 method=peqa dtype=bfloat16 ===
files: ['config.json', 'generation_config.json', 'model.safetensors', 'tokenizer.json', 'tokenizer_config.json']
quant_method: compressed-tensors | format: pack-quantized | groups: ['group_0']
weights: {"actorder": null, "dynamic": false, "group_size": 64, "num_bits": 8, "strategy": "group", "symmetric": false, "type": "int"}
n targets: 210 first: ['model.layers.0.self_attn.q_proj', 'model.layers.0.self_attn.k_proj', 'model.layers.0.self_attn.v_proj']
  model.layers.0.mlp.down_proj.weight_packed: (576, 384) torch.int32
  model.layers.0.mlp.down_proj.weight_scale: (576, 24) torch.bfloat16
  model.layers.0.mlp.down_proj.weight_zero_point: (144, 24) torch.int32
  model.layers.0.mlp.down_proj.weight_shape: (2,) torch.int64
logits bit-for-bit equal (qpeft merged vs transformers-loaded export): True | max abs diff: 0.0
qpeft    : 'The capital of France is Paris. It is known for the Eiffel Tower and fine'
exported : 'The capital of France is Paris. It is known for the Eiffel Tower and fine'
greedy generations equal: True
Evidence: SmolLM2-135M 2/5/7-bit and partial-target exports reload bit for bit
bits=2 dtype=torch.bfloat16 targets=['q_proj', 'k_proj', 'v_proj', 'o_proj', 'gate_proj', 'up_proj', 'down_proj'] (210 layers): transformers logits bit-for-bit equal: True
bits=5 dtype=torch.float16 targets=['q_proj', 'k_proj', 'v_proj', 'o_proj', 'gate_proj', 'up_proj', 'down_proj'] (210 layers): transformers logits bit-for-bit equal: True
bits=7 dtype=torch.bfloat16 targets=['q_proj', 'k_proj', 'v_proj', 'o_proj', 'gate_proj', 'up_proj', 'down_proj'] (210 layers): transformers logits bit-for-bit equal: True
bits=4 dtype=torch.bfloat16 targets=['q_proj', 'v_proj'] (60 layers): transformers logits bit-for-bit equal: True
Evidence: Refusal transcript on SmolLM2-135M (tied lm_head, QA-LoRA, unmerged, fp32 scale, bits=1): all refused, no dir written
SmolLM2 tie_word_embeddings: True
[tied-lm_head] refused: UnsupportedSchemeError: lm_head: the model a loader builds from config.json ties its weight to ['model.embed_tokens.weight'], which stays float. Quantize without it (target_modules), or untie the weights, then export.
[tied-lm_head] output dir exists afterwards: False
[qa-lora-fractional-z] refused: UnsupportedSchemeError: model.layers.0.self_attn.q_proj: its zero-points are fractional (a QA-LoRA fold, z' = z - delta / s). compressed-tensors stores integer zero-points, and rounding them would break fake_quant == merge. Keep the qpeft artifact (save_pretrained) or run it on tinygemm (qpeft.kernels.to_tinygemm).
[qa-lora-fractional-z] output dir exists afterwards: False
[unmerged] refused: UnsupportedSchemeError: model.layers.0.mlp.down_proj: compressed-tensors export needs a merged layer; call merge_and_unload() first.
[unmerged] output dir exists afterwards: False
[fp32-scale-in-bf16-model] refused: UnsupportedSchemeError: model.layers.3.mlp.gate_proj: its scales are torch.float32, but the checkpoint's dtype is torch.bfloat16, and a loader casts the scales to it. Quantize and merge the whole model in one dtype.
[fp32-scale-in-bf16-model] output dir exists afterwards: False
[bits-1] refused: UnsupportedSchemeError: model.layers.0.mlp.down_proj: compressed-tensors export takes bits (2, 3, 4, 5, 6, 7, 8), got 1.
[bits-1] output dir exists afterwards: False
Evidence: vLLM 0.31 arm64 CPU: 4-bit export accepted, CPUWNA16 kernel picked, weights loaded, forward fails on the missing cpu_gemm_wna16 op
(MainProcess pid=1) INFO 10-12 14:21:06 [api_utils.py:286] non-default args: {'dtype': 'bfloat16', 'max_model_len': 256, 'disable_log_stats': True, 'enforce_eager': True, 'model': '/ckpt'}
W1012 14:21:09.764000 1 torch/_library/triton.py:53] Triton not available, find_triton_kernels = []
(MainProcess pid=1) INFO 10-12 14:21:09 [model.py:699] Resolved architecture: LlamaForCausalLM
(MainProcess pid=1) INFO 10-12 14:21:09 [model.py:2109] Using max model len 256
(MainProcess pid=1) WARNING 10-12 14:21:10 [indexer_topk.py:35] Failed to import the DeepSelect extension (vllm._deepselect_C): No module named 'vllm._deepselect_C'
(MainProcess pid=1) INFO 10-12 14:21:10 [scheduler.py:308] Chunked prefill is enabled with max_num_batched_tokens=4096.
(MainProcess pid=1) WARNING 10-12 14:21:10 [vllm.py:1706] Enforce eager set, disabling torch.compile, CUDAGraphs, and JIT kernel warmup. This is equivalent to setting -cc.mode=none -cc.cudagraph_mode=none and --kernel_config.enable_jit_warmup=False
(MainProcess pid=1) WARNING 10-12 14:21:10 [vllm.py:1750] Inductor compilation was disabled by user settings, optimizations settings that are only active during inductor compilation will be ignored.
(MainProcess pid=1) INFO 10-12 14:21:10 [kernel.py:408] Final IR op priority after setting platform defaults: IrOpPriorityConfig(rms_norm=['native'], fused_add_rms_norm=['native'], gelu_and_mul_sparse=['native'])
(MainProcess pid=1) WARNING 10-12 14:21:10 [vllm.py:747] Model Runner V2 requires Triton; using the V1 model runner instead.
(MainProcess pid=1) INFO 10-12 14:21:10 [compilation.py:331] Enabled custom fusions: norm_quant, act_quant
W1012 14:21:13.621000 87 torch/_library/triton.py:53] Triton not available, find_triton_kernels = []
(EngineCore pid=87) INFO 10-12 14:21:13 [core.py:129] Initializing a V1 LLM engine (v0.31.0) with config: model='/ckpt', speculative_config=None, tokenizer='/ckpt', skip_tokenizer_init=False, tokenizer_mode=auto, revision=None, tokenizer_revision=None, trust_remote_code=False, dtype=torch.bfloat16, max_seq_len=256, download_dir=None, load_format=auto, tensor_parallel_size=1, pipeline_parallel_size=1, data_parallel_size=1, decode_context_parallel_size=1, dcp_comm_backend=ag_rs, disable_custom_all_reduce=True, quantization=compressed-tensors, quantization_config=None, enforce_eager=True, aux_output_config=AuxOutputConfig(enable_return_routed_experts=False, max_bytes=None), kv_cache_dtype=auto, device_config=cpu, structured_outputs_config=StructuredOutputsConfig(backend='auto', disable_any_whitespace=False, disable_additional_properties=False, reasoning_parser='', reasoning_parser_plugin='', enable_in_reasoning=False), observability_config=ObservabilityConfig(show_hidden_metrics_for_version=None, otlp_traces_endpoint=None, collect_detailed_traces=None, per_request_spec_decode_metrics='none', kv_cache_metrics=False, kv_cache_metrics_sample=0.01, cudagraph_metrics=False, enable_layerwise_nvtx_tracing=False, enable_mfu_metrics=False, enable_mm_processor_stats=False, enable_logging_iteration_details=False, jit_monitor_mode='warn', jit_monitor_verbose=False), seed=0, served_model_name=/ckpt, enable_prefix_caching=True, enable_chunked_prefill=True, pooler_config=None, compilation_config={'mode': <CompilationMode.NONE: 0>, 'debug_dump_path': None, 'cache_dir': '', 'compile_cache_save_format': 'binary', 'backend': 'inductor', 'custom_ops': ['all', '+gelu', '+gelu_tanh', '+gelu_and_mul'], 'ir_enable_torch_wrap': False, 'splitting_ops': [], 'compile_mm_encoder': False, 'cudagraph_mm_encoder': False, 'encoder_cudagraph_token_budgets': [], 'encoder_cudagraph_max_vision_items_per_batch': 0, 'encoder_cudagraph_max_frames_per_batch': None, 'compile_sizes': None, 'compile_ranges_endpoints': [4096], 'inductor_compile_config': {'enable_auto_functionalized_v2': False}, 'inductor_passes': {}, 'cudagraph_mode': <CUDAGraphMode.NONE: 0>, 'cudagraph_num_of_warmups': 0, 'cudagraph_capture_sizes': [], 'cudagraph_copy_inputs': False, 'cudagraph_specialize_lora': True, 'use_inductor_graph_partition': False, 'pass_config': {'fuse_norm_quant': True, 'fuse_act_quant': True, 'fuse_attn_quant': False, 'enable_sp': False, 'fuse_gemm_comms': False, 'fuse_allreduce_rms': False, 'enable_qk_norm_rope_fusion': False, 'fuse_rope_kvcache_cat_mla': False, 'fuse_act_padding': False, 'fuse_qk_norm_rope_kvcache': False}, 'max_cudagraph_capture_size': None, 'dynamic_shapes_config': {'type': <DynamicShapesType.BACKED: 'backed'>, 'evaluate_guards': False, 'assume_32_bit_indexing': False}, 'local_cache_dir': None, 'fast_moe_cold_start': False, 'static_all_moe_layers': []}, kernel_config=KernelConfig(ir_op_priority=IrOpPriorityConfig(rms_norm=['native'], fused_add_rms_norm=['native'], gelu_and_mul_sparse=['native']), enable_flashinfer_autotune=True, enable_cutedsl_warmup=True, enable_jit_warmup=False, moe_backend='auto', sparse_indexer_topk_backend='auto', linear_backend='auto', linear_backend_per_quant=None)
(EngineCore pid=87) INFO 10-12 14:21:13 [multiproc_executor.py:153] DP group leader: node_rank=0, node_rank_within_dp=0, master_addr=127.0.0.1, mq_connect_ip=172.17.0.2 (local), world_size=1, local_world_size=1
(EngineCore pid=87) INFO 10-12 14:21:13 [ompmultiprocessing.py:185] OpenMP thread binding info: 
(EngineCore pid=87) INFO 10-12 14:21:13 [ompmultiprocessing.py:185] 	VLLM_CPU_OMP_THREADS_BIND='auto', auto_setup=True, skip_setup=False
(EngineCore pid=87) INFO 10-12 14:21:13 [ompmultiprocessing.py:185] 	local_world_size=1, reserve_cpu_num=1
(EngineCore pid=87) INFO 10-12 14:21:13 [ompmultiprocessing.py:185] 	local_rank=0, core ids=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
(EngineCore pid=87) INFO 10-12 14:21:13 [ompmultiprocessing.py:185] 	reserved_cpus=[11]
W1012 14:21:18.078000 130 torch/_library/triton.py:53] Triton not available, find_triton_kernels = []
/opt/venv/lib/python3.12/site-packages/vllm/triton_utils/dispatcher.py:138: FutureWarning: `torch.distributed.reduce_op` is deprecated, please use `torch.distributed.ReduceOp` instead
  if hasattr(value, "__dict__"):
/opt/venv/lib/python3.12/site-packages/vllm/triton_utils/dispatcher.py:139: FutureWarning: `torch.distributed.reduce_op` is deprecated, please use `torch.distributed.ReduceOp` instead
  wrapper = lookup(vars(value).get("kernel"))
(VllmWorker-0 pid=130) WARNING 10-12 14:21:18 [indexer_topk.py:35] Failed to import the DeepSelect extension (vllm._deepselect_C): No module named 'vllm._deepselect_C'
get_mempolicy: Operation not permitted
[W1012 14:21:18.190060353 utils.cpp:41] Warning: numa_migrate_pages failed. errno: 1 (function init_cpu_memory_env)
set_mempolicy: Operation not permitted
[W1012 14:21:18.190072728 utils.cpp:65] Warning: numa_set_membind failed. errno: 1 (function init_cpu_memory_env)
(VllmWorker-0 pid=130) WARNING 10-12 14:21:18 [vllm.py:747] Model Runner V2 requires Triton; using the V1 model runner instead.
(Worker pid=130) INFO 10-12 14:21:18 [parallel_state.py:1825] world_size=1 rank=0 local_rank=0 distributed_init_method=file:///tmp/vllm_dist_b6e9eba3bfd34751865f809f6ac6ec31 backend=gloo
(Worker pid=130) INFO 10-12 14:21:18 [parallel_state.py:2269] rank 0 in world size 1 is assigned as DP rank 0, PP rank 0, PCP rank 0, TP rank 0, ETP rank 0, EP rank N/A, EPLB rank N/A
(Worker pid=130) INFO 10-12 14:21:18 [cpu_model_runner.py:69] Starting to load model /ckpt...
(Worker pid=130) INFO 10-12 14:21:18 [__init__.py:870] Using CPUWNA16LinearKernel for mixed-precision linear
(Worker pid=130) INFO 10-12 14:21:18 [compressed_tensors_wNa16.py:138] Using CPUWNA16LinearKernel for CompressedTensorsWNA16
(Worker pid=130) WARNING 10-12 14:21:18 [compilation.py:1350] Op 'gelu' not present in model, enabling with '+gelu' has no effect
(Worker pid=130) WARNING 10-12 14:21:18 [compilation.py:1350] Op 'gelu_tanh' not present in model, enabling with '+gelu_tanh' has no effect
(Worker pid=130) WARNING 10-12 14:21:18 [compilation.py:1350] Op 'gelu_and_mul' not present in model, enabling with '+gelu_and_mul' has no effect
(Worker pid=130) INFO 10-12 14:21:18 [weight_utils.py:1063] Filesystem type for checkpoints: OVERLAY. Checkpoint size: 0.11 GiB. Available RAM: 20.35 GiB.
(Worker pid=130) INFO 10-12 14:21:18 [w

... [26340 bytes truncated] ...

h_partition': False, 'pass_config': {'fuse_norm_quant': True, 'fuse_act_quant': True, 'fuse_attn_quant': False, 'enable_sp': False, 'fuse_gemm_comms': False, 'fuse_allreduce_rms': False, 'enable_qk_norm_rope_fusion': False, 'fuse_rope_kvcache_cat_mla': False, 'fuse_act_padding': False, 'fuse_qk_norm_rope_kvcache': False}, 'max_cudagraph_capture_size': None, 'dynamic_shapes_config': {'type': <DynamicShapesType.BACKED: 'backed'>, 'evaluate_guards': False, 'assume_32_bit_indexing': False}, 'local_cache_dir': None, 'fast_moe_cold_start': False, 'static_all_moe_layers': []}, kernel_config=KernelConfig(ir_op_priority=IrOpPriorityConfig(rms_norm=['native'], fused_add_rms_norm=['native'], gelu_and_mul_sparse=['native']), enable_flashinfer_autotune=True, enable_cutedsl_warmup=True, enable_jit_warmup=False, moe_backend='auto', sparse_indexer_topk_backend='auto', linear_backend='auto', linear_backend_per_quant=None), 
(EngineCore pid=87) ERROR 10-12 14:21:20 [dump_input.py:79] Dumping scheduler output for model execution: SchedulerOutput(scheduled_new_reqs=[NewRequestData(req_id=0-bf1074f0,prompt_token_ids_len=5,prefill_token_ids_len=None,mm_features=[],sampling_params=SamplingParams(n=1, presence_penalty=0.0, frequency_penalty=0.0, repetition_penalty=1.0, temperature=0.0, watermarking=True, top_p=1.0, top_k=0, min_p=0.0, seed=None, stop=[], stop_token_ids=[], bad_words=[], thinking_token_budget=None, include_stop_str_in_output=False, ignore_eos=False, max_tokens=12, min_tokens=0, logprobs=1, prompt_logprobs=1, skip_special_tokens=True, spaces_between_special_tokens=True, structured_outputs=None, extra_args=None),block_ids=([1],),num_computed_tokens=0,lora_request=None,prompt_embeds_shape=None)], scheduled_cached_reqs=CachedRequestData(req_ids=[],resumed_req_ids=set(),new_token_ids_lens=[],all_token_ids_lens={},new_block_ids=[],num_computed_tokens=[],num_output_tokens=[]), num_scheduled_tokens={0-bf1074f0: 5}, total_num_scheduled_tokens=5, scheduled_spec_decode_tokens={}, scheduled_encoder_inputs={}, num_common_prefix_blocks=[1], finished_req_ids=[], free_encoder_mm_hashes=[], scheduled_encoder_input_stats=null, preempted_req_ids=[], has_structured_output_requests=false, pending_structured_output_tokens=false, num_invalid_spec_tokens=null, kv_connector_metadata=null, has_sync_kv_loads=false, aux_output_connector_metadata=null, ec_connector_metadata=null, ec_manager_metadata=null, new_block_ids_to_zero=null, kv_cache_block_copies=null, block_table_updates=null, kv_connector_block_state=null, num_spec_tokens_to_schedule=0)
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435] EngineCore encountered a fatal error.
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435] Traceback (most recent call last):
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]   File "/opt/venv/lib/python3.12/site-packages/vllm/v1/engine/core.py", line 1419, in run_engine_core
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]     engine_core.run_busy_loop()
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]   File "/opt/venv/lib/python3.12/site-packages/vllm/v1/fault_tolerance/engine_core_sentinel.py", line 179, in run_with_fault_tolerance
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]     busy_loop_func(self)
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]   File "/opt/venv/lib/python3.12/site-packages/vllm/v1/engine/core.py", line 1478, in run_busy_loop
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]     self._process_engine_step()
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]   File "/opt/venv/lib/python3.12/site-packages/vllm/v1/engine/core.py", line 1529, in _process_engine_step
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]     outputs, model_executed = self.step_fn()
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]                               ^^^^^^^^^^^^^^
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]   File "/opt/venv/lib/python3.12/site-packages/vllm/v1/engine/core.py", line 647, in step
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]     model_output = future.result()
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]                    ^^^^^^^^^^^^^^^
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]   File "/opt/venv/lib/python3.12/site-packages/vllm/v1/executor/multiproc_executor.py", line 99, in result
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]     return super().result()
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]            ^^^^^^^^^^^^^^^^
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]   File "/opt/uv/python/cpython-3.12.15-linux-aarch64-gnu/lib/python3.12/concurrent/futures/_base.py", line 449, in result
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]     return self.__get_result()
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]            ^^^^^^^^^^^^^^^^^^^
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]   File "/opt/uv/python/cpython-3.12.15-linux-aarch64-gnu/lib/python3.12/concurrent/futures/_base.py", line 401, in __get_result
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]     raise self._exception
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]   File "/opt/venv/lib/python3.12/site-packages/vllm/v1/executor/multiproc_executor.py", line 103, in _wait_for_response
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]     response = self.aggregate(self.get_response())
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]                               ^^^^^^^^^^^^^^^^^^^
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]   File "/opt/venv/lib/python3.12/site-packages/vllm/v1/executor/multiproc_executor.py", line 439, in get_response
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435]     raise RuntimeError(
(EngineCore pid=87) ERROR 10-12 14:21:20 [core.py:1435] RuntimeError: Worker failed with error ''_OpNamespace' '_C' object has no attribute 'cpu_gemm_wna16'', please check the stack trace above for the root cause
Traceback (most recent call last):
  File "/scripts/vllm_serve.py", line 34, in <module>
    main()
  File "/scripts/vllm_serve.py", line 11, in main
    out = llm.generate(prompts, SamplingParams(temperature=0, max_tokens=12, prompt_logprobs=1, logprobs=1))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.12/site-packages/vllm/entrypoints/llm.py", line 475, in generate
(Worker pid=130) INFO 10-12 14:21:20 [multiproc_executor.py:838] Parent process exited, terminating worker queues
(EngineCore pid=87) INFO 10-12 14:21:20 [multiproc_executor.py:474] [shutdown] Executor: waiting for worker exit count=1
    return self._run_completion(
           ^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.12/site-packages/vllm/entrypoints/offline_utils.py", line 357, in _run_completion
    return self._run_engine(use_tqdm=use_tqdm, output_type=output_type)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.12/site-packages/vllm/entrypoints/offline_utils.py", line 603, in _run_engine
    step_outputs = self.llm_engine.step()
                   ^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.12/site-packages/vllm/v1/engine/llm_engine.py", line 314, in step
    outputs = self.engine_core.get_output()
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.12/site-packages/vllm/v1/engine/core_client.py", line 988, in get_output
    raise self._format_exception(outputs) from None
vllm.v1.engine.exceptions.EngineDeadError: EngineCore encountered an issue. See stack trace (above) for the root cause.
(MainProcess pid=1) INFO 10-12 14:21:20 [utils.py:638] [shutdown] Process manager: send sigterm to process EngineCore

Processed prompts:   0%|          | 0/2 [00:00<?, ?it/s, est. speed input: 0.00 toks/s, output: 0.00 toks/s]
/opt/uv/python/cpython-3.12.15-linux-aarch64-gnu/lib/python3.12/multiprocessing/resource_tracker.py:279: UserWarning: resource_tracker: There appear to be 1 leaked shared_memory objects to clean up at shutdown
  warnings.warn('resource_tracker: There appear to be %d '
  • Evidence: vLLM 0.31 arm64 CPU: 8-bit rejected by the CPU kernel (uint4 only) (local file: ~/.no-mistakes/evidence/01M4R72T4XAWXNMKNFKK2NZAED/vllm-0.31-cpu-arm64-8bit.log)
Evidence: vLLM 0.31 x86_64 CPU under emulation: SIGILL (AVX-512 required)
(MainProcess pid=1) INFO 10-12 14:25:42 [api_utils.py:286] non-default args: {'dtype': 'bfloat16', 'max_model_len': 256, 'disable_log_stats': True, 'enforce_eager': True, 'model': '/ckpt'}
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110] Error in inspecting model architecture 'LlamaForCausalLM'
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110] Traceback (most recent call last):
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110]   File "/opt/venv/lib/python3.12/site-packages/vllm/model_executor/models/registry.py", line 1550, in _run_in_subprocess
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110]     returned.check_returncode()
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110]   File "/opt/uv/python/cpython-3.12.15-linux-x86_64-gnu/lib/python3.12/subprocess.py", line 502, in check_returncode
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110]     raise CalledProcessError(self.returncode, self.args, self.stdout,
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110] subprocess.CalledProcessError: Command '['/opt/venv/bin/python3', '-m', 'vllm.model_executor.models.registry']' died with <Signals.SIGILL: 4>.
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110] 
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110] The above exception was the direct cause of the following exception:
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110] 
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110] Traceback (most recent call last):
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110]   File "/opt/venv/lib/python3.12/site-packages/vllm/model_executor/models/registry.py", line 1108, in _try_inspect_model_cls
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110]     return model.inspect_model_cls()
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110]            ^^^^^^^^^^^^^^^^^^^^^^^^^
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110]   File "/opt/venv/lib/python3.12/site-packages/vllm/logging_utils/log_time.py", line 18, in _wrapper
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110]     result = func(*args, **kwargs)
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110]              ^^^^^^^^^^^^^^^^^^^^^
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110]   File "/opt/venv/lib/python3.12/site-packages/vllm/model_executor/models/registry.py", line 1069, in inspect_model_cls
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110]     mi = _run_in_subprocess(
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110]          ^^^^^^^^^^^^^^^^^^^
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110]   File "/opt/venv/lib/python3.12/site-packages/vllm/model_executor/models/registry.py", line 1553, in _run_in_subprocess
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110]     raise RuntimeError(
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110] RuntimeError: Error raised in subprocess:
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110] <frozen runpy>:128: RuntimeWarning: 'vllm.model_executor.models.registry' found in sys.modules after import of package 'vllm.model_executor.models', but prior to execution of 'vllm.model_executor.models.registry'; this may result in unpredictable behaviour
(MainProcess pid=1) ERROR 10-12 14:26:22 [registry.py:1110] 
Traceback (most recent call last):
  File "/scripts/vllm_serve.py", line 34, in <module>
    main()
  File "/scripts/vllm_serve.py", line 10, in main
    llm = LLM(path, dtype=dtype, max_model_len=256, enforce_eager=True)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.12/site-packages/vllm/entrypoints/llm.py", line 344, in __init__
    self.llm_engine = LLMEngine.from_engine_args(
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.12/site-packages/vllm/v1/engine/llm_engine.py", line 177, in from_engine_args
    vllm_config = engine_args.create_engine_config(usage_context)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.12/site-packages/vllm/engine/arg_utils.py", line 2183, in create_engine_config
    model_config = self.create_model_config()
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.12/site-packages/vllm/engine/arg_utils.py", line 1918, in create_model_config
    return ModelConfig(
           ^^^^^^^^^^^^
  File "/opt/venv/lib/python3.12/site-packages/pydantic/_internal/_dataclasses.py", line 121, in __init__
    s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s)
pydantic_core._pydantic_core.ValidationError: 1 validation error for ModelConfig
  Value error, Model architectures ['LlamaForCausalLM'] failed to be inspected. Please check the logs for more details. [type=value_error, input_value=ArgsKwargs((), {'model': ...nderer_num_workers': 1}), input_type=ArgsKwargs]
    For further information visit https://errors.pydantic.dev/2.13/v/value_error

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 warning
  • ⚠️ qpeft/export/compressed_tensors.py:135 - The export always writes &#34;ignore&#34;: []. vLLM's CompressedTensorsConfig.get_scheme calls find_matched_target for every LinearBase layer that is not in ignore, and that function raises ValueError: Unable to find matching target for &lt;layer&gt; in the compressed-tensors config when no target matches [unconfirmed: from memory of vLLM source, not checked in this environment; this is also the known failure for MoE mlp.gate layers that are missing from ignore]. Concrete path: export merged_peqa(4, targets=[&#34;q_proj&#34;, &#34;v_proj&#34;]), or any MoE model whose router this exporter requires to stay unquantized (a ReplicatedLinear in vLLM), then call vllm.LLM(path). The unquantized o_proj / gate_up_proj / router is a LinearBase with no matching target, so vLLM refuses to load. The vLLM validation in README/AGENTS.md used all block linears on dense Qwen3-0.6B, so it did not cover this case. The failure is loud, not a silent approximation, but it blocks the stated goal ("so vLLM can serve a merged qpeft model") for every partial-target or MoE export. Likely fix: build the skeleton once and list, in full-name form, every plain nn.Linear that is not quantized in ignore (llm-compressor checkpoints always list such layers, for example lm_head). This changes the written quantization_config, so the author should confirm it against vLLM source and with a partial-target vLLM run before making the change. The same ignore list is also read by transformers + compressed-tensors (tests/test_compressed_tensors_load.py), and that path must stay bitwise-equal.
✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 5 of 6 scenarios driven live against the product
Scenario Result Live Evidence
User merges a PEQA / EfficientQAT model, calls export_compressed_tensors, and transformers + compressed-tensors reload it with logits equal bit for bit (real SmolLM2-135M, 3/4/8 bits, bf16 and fp16) ✅ pass live smollm2-export-reload-transcript.txt: logits bit-for-bit equal True, greedy generations equal True for all three runs; config.json shows quant_method compressed-tensors, pack-quantized, symmetric fals…
Dense packing at odd widths and the full 2..8 range: 2/5/7-bit real-model exports and bits 2-8 on tiny Llama (bf16/fp16, PEQA and EfficientQAT) reload bit for bit through transformers ✅ pass live smollm2-more-bits-transcript.txt; pytest tests/test_compressed_tensors_load.py in pytest-export-load.txt
Partial targets (q_proj, v_proj only) export reloads bit for bit through transformers ✅ pass live smollm2-more-bits-transcript.txt (60 layers, logits bit-for-bit equal True)
Adversarial: a tied lm_head, a QA-LoRA fractional zero-point, an unmerged model, an fp32 scale in a bf16 model, and bits=1 are each refused with UnsupportedSchemeError and no output directory is writt… ✅ pass live refusals-transcript.txt (all 5 refused; 'output dir exists afterwards: False'); refusal tests in pytest-export-load.txt (also the MoE-router subclass and a non-HF model)
GPTQ exporter after the refactor into _checkpoint.py still writes checkpoints that gptqmodel + optimum load bit for bit ✅ pass live pytest tests/test_gptq_load.py (pytest-gptq-load.txt, 18 passed) and pytest tests/test_gptq_export.py
vLLM serves the 4-bit compressed-tensors export and generates text close to the exact model ⏸️ untested no No CUDA GPU on this Apple Silicon host, and vLLM has no macOS wheel. I ran vLLM 0.31 from its official CPU docker images in disposable containers. The arm64 image parsed the export and loaded all weig…
  • pytest tests/test_compressed_tensors_export.py tests/test_compressed_tensors_load.py tests/test_gptq_export.py (102 passed, compressed-tensors 0.19, transformers 5.19, CPU)
  • pytest tests/test_gptq_load.py with gptqmodel + optimum installed (18 passed): the GPTQ refactor still loads bit for bit
  • Real-model flow on HuggingFaceTB/SmolLM2-135M: PEQA 4-bit bf16, EfficientQAT (Block-AP + E2E-QP) 3-bit fp16, PEQA 8-bit bf16. Each run trains, calls merge_and_unload and export_compressed_tensors, saves the tokenizer, then reloads with AutoModelForCausalLM. It compares logits and greedy generations and inspects config.json and tensor dtypes/shapes
  • Real-model PEQA exports at 2-bit bf16, 5-bit fp16 and 7-bit bf16, plus a partial-target (q_proj, v_proj) export, each reloaded through transformers with a bit-for-bit logits check
  • Refusal checks on SmolLM2-135M: tied lm_head (targets=None), a QA-LoRA fold with fractional zero-points, an unmerged model, an fp32 scale in a bf16 model, and bits=1. Each one must refuse and write no output dir
  • vLLM 0.31 vllm/vllm-openai-cpu:latest-arm64 in a throwaway docker container: LLM(export, dtype=...) on the 4-bit, 8-bit and 3-bit exports
  • vLLM 0.31 vllm/vllm-openai-cpu:latest-x86_64 under amd64 emulation on the 4-bit export
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

export_compressed_tensors writes the asymmetric pack-quantized WNA16 layout
(weight_packed, weight_scale, weight_zero_point, weight_shape) that vLLM and
transformers + compressed-tensors load, with one config group per grid that
targets the quantized layers by full name. The scale keeps the model's dtype,
so nothing is cast; fractional zero-points, linear subclasses and tied
weights are refused.

The merged-layer lookup, integer zero-points, loader skeleton and checkpoint
writing move from gptq.py into export/_checkpoint.py, shared by both
exporters. The export test models move into tests/export_models.py and are
built in their dtype like from_pretrained builds them (fp32 rotary inv_freq),
so a loaded export matches them bit for bit.
…he checkpoint

Loaders make weight_scale in the dtype that save_pretrained writes to
config.json, so a scale in another dtype would be cast on load.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant