Skip to content

feat(quant): acext INT8 W8A8 scaled-MM linear kernel for T-Head PPU - #511

Open
ziyuhu214 wants to merge 1 commit into
flagos-ai:mainfrom
ziyuhu214:pr/acext-int8-linear
Open

ziyuhu214 wants to merge 1 commit into
flagos-ai:mainfrom
ziyuhu214:pr/acext-int8-linear

Conversation

@ziyuhu214

Copy link
Copy Markdown

What

Adds AcextInt8ScaledMMLinearKernel, an INT8 W8A8 scaled-MM linear kernel backed
by the T-Head acext vendor library, and registers it as the preferred INT8
candidate on PPU.

Unlike the cutlass/triton kernels, acext.int8_gemm takes weights in row-major
[N, K]
, so process_weights_after_loading keeps the checkpoint layout as-is
rather than transposing.

is_supported() gates on two things — acext importable and
vendor_name == "thead" — so the kernel is inert on every other platform.

Registration order matters here, and it is easy to get wrong

The kernel is registered after register_fl_w8a8_linear_kernel(), not before.
That helper prepends FLW8A8DynamicLinearKernel at index 0, and its
is_supported() does not gate on vendor, so registering acext any earlier leaves
FLW8A8 ahead of it — and can_implement() accepts the same
channelwise + dynamic + symmetric config DeepSeek-V4 INT8 uses, so acext would
never be selected on PPU and the vendor kernel would be silently unused.

Verified both ways on hardware:

old order: ['FLW8A8DynamicLinearKernel', 'AcextInt8ScaledMMLinearKernel']
           -> selected: FLW8A8DynamicLinearKernel   (acext shadowed)

new order: ['AcextInt8ScaledMMLinearKernel', 'FLW8A8DynamicLinearKernel',
            'CutlassInt8ScaledMMLinearKernel', 'TritonInt8ScaledMMLinearKernel']
           -> selected: AcextInt8ScaledMMLinearKernel

The insert also sits outside the OOT not in _POSSIBLE_INT8_KERNELS guard, which
would otherwise skip acext whenever the OOT list was already populated, and is
idempotent so repeated calls cannot stack duplicates.

Testing

Runtime assertions on PPU (vendor_name=thead, acext.int8_gemm present):

  • acext is at index 0 after add_oot_quant_kernel()
  • FLW8A8DynamicLinearKernel is retained as a fallback at index 1
  • a second add_oot_quant_kernel() call does not duplicate acext
  • for Int8ScaledMMLinearLayerConfig(is_static_input_scheme=False, is_channelwise=True, input_symmetric=True),
    the first kernel passing both is_supported() and can_implement() is acext

ruff check / ruff format --check clean.

I have not re-run an end-to-end accuracy or throughput comparison against the
FlagGems W8A8 kernel for this PR — the change verified here is selection order and
gating.

Environment: T-Head PPU-ZW810E, torch 2.10.0, vLLM 0.24.0.

Port the vendor fork's PPUInt8ScaledMMLinearKernel (acext branch):
row-major int8 weights fed straight to acext.int8_gemm, per-token
dynamic activation quant via the _C::dynamic_scaled_int8_quant bridge.
Symmetric path only; asymmetric configs report can_implement=False and
fall back to the triton kernel.

Registered first in _POSSIBLE_INT8_KERNELS[OOT] so it wins the kernel
oracle on thead when acext is installed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Register the kernel after register_fl_w8a8_linear_kernel(), not before.
That helper prepends FLW8A8DynamicLinearKernel at index 0 of the OOT INT8
candidates, and its is_supported() does not gate on vendor, so an earlier
insert leaves FLW8A8 ahead of acext and acext is never selected on PPU --
its can_implement() accepts the same channelwise/dynamic/symmetric config
DeepSeek-V4 INT8 uses.

Also moved out of the 'OOT not in _POSSIBLE_INT8_KERNELS' guard, which
skipped acext entirely whenever the OOT list was already populated, and
made the insert idempotent so repeated calls cannot stack duplicates.

Sort the import block to satisfy ruff I001.
Copilot AI lite review requested due to automatic review settings September 14, 2026 07:48
@github-actions github-actions Bot added the core label Sep 14, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unaligned shapes can fail at runtime, scale handling needs correction, and key behavior lacks automated coverage.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds a T-Head PPU acext INT8 W8A8 scaled-MM kernel and prioritizes it in kernel selection.

Changes:

  • Implements row-major weight handling and acext.int8_gemm integration.
  • Gates usage on T-Head hardware and acext availability.
  • Registers the kernel idempotently ahead of existing INT8 candidates.
File summaries
File Review findings
vllm_fl/quantization/acext_int8_linear.py Critical (2 votes): Unaligned dimensions can abort instead of falling back. Moderate (1 vote): Normalize [N, 1] scales to a contiguous FP32 vector. Nit (2 votes): Add automated coverage for gating, weight handling, argument ordering, and idempotent insertion.
vllm_fl/quantization/quant_linear.py Nit (1 vote): Add automated coverage verifying acext registry priority and idempotency.
Review details

Suppressed comments (2)

vllm_fl/quantization/acext_int8_linear.py:103

  • This forwards the checkpoint weight_scale shape unchanged. The canonical compressed-tensors scale is [N, 1] (tests/unit_tests/quantization/test_w8a8_linear.py:209-212), while the neighboring scaled-MM adapter normalizes per-output scales to [N] (vllm_fl/quantization/w8a8/linear.py:78-90). Passing the 2-D tensor to acext.int8_gemm can make the vendor kernel reject or misinterpret the scale; flatten it to a contiguous fp32 vector before storing it.
        weight_scale = getattr(layer, w_s_name)
        if is_fused_module and not self.config.is_channelwise:
            weight_scale = convert_to_channelwise(weight_scale, layer.logical_widths)

vllm_fl/quantization/quant_linear.py:100

  • The ordering and idempotency branch is the behavior this PR is intended to guarantee, but the existing quantization tests do not exercise it: test_oot_quant_registry_inherits_mxfp8_candidates monkeypatches register_fl_w8a8_linear_kernel and only checks the MXFP8 registry. Add a unit test that stubs the acext import/platform and asserts [AcextInt8ScaledMMLinearKernel, FLW8A8DynamicLinearKernel, ...] remains unchanged after a second call; otherwise a registry change can silently shadow the vendor kernel while hardware-only checks miss it.
        int8_candidates = _POSSIBLE_INT8_KERNELS.setdefault(PlatformEnum.OOT, [])
        if AcextInt8ScaledMMLinearKernel not in int8_candidates:
            int8_candidates.insert(0, AcextInt8ScaledMMLinearKernel)
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

out_dtype: torch.dtype,
bias: torch.Tensor | None = None,
) -> torch.Tensor:
assert b.shape[0] % 16 == 0 and b.shape[1] % 16 == 0
Comment on lines +84 to +87
def can_implement(cls, c: Int8ScaledMMLinearLayerConfig) -> tuple[bool, str | None]:
if not c.input_symmetric:
return False, "acext kernel supports symmetric quantization only."
return True, None
@CLAassistant

CLAassistant commented Sep 14, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants