본문으로 건너뛰기

[sglang] SGLang, AMD GPU에서 Quark 모델의 FP4 GEMM 성능 최적화: AITER ASM 활용

PR 링크: sgl-project/sglang#39598 상태: Merged | 변경: +328 / -0

들어가며

최근 SGLang 프로젝트에서는 AMD GPU 환경에서 Quark 모델의 FP4 GEMM(General Matrix Multiply) 연산 성능을 개선하기 위한 중요한 PR이 병합되었습니다. 기존에는 ROCm 환경에서 Quark W4A4 MXFP4 연산 시 AITER의 Triton 기반 gemm_afp4wfp4 커널을 사용해왔습니다. 이 커널은 AITER의 gemm_a4w4 및 vLLM에서 사용하는 a4w4_blockscale_tuned_gemm.csv 튜닝 테이블을 활용하지 못했습니다. 이로 인해 SGLang은 vLLM과 같은 최적화된 모델별 CK/ASM 튜닝을 적용하지 못하고 별도의 Triton 튜닝 테이블을 관리해야 하는 비효율성이 있었습니다.

본 PR은 이러한 문제를 해결하기 위해, 사용자가 옵트인(opt-in)할 수 있는 새로운 경로를 추가했습니다. 이 경로는 AITER의 gemm_a4w4 커널을 사용하여 SGLang이 vLLM과 동일한 튜닝된 커널을 활용할 수 있도록 합니다. 이를 통해 AMD GPU에서의 Quark 모델 추론 성능을 크게 향상시킬 수 있습니다.

코드 분석

1. 환경 변수 추가 (python/sglang/srt/environ.py)

새로운 최적화 경로를 활성화하기 위한 환경 변수가 추가되었습니다.

--- a/python/sglang/srt/environ.py
+++ b/python/sglang/srt/environ.py
@@ -973,6 +973,9 @@ class Envs:
     SGLANG_ROCM_SMALLM_ROUTER = EnvBool(True)
     SGLANG_ROCM_SMALLM_MOE = EnvBool(True)
     SGLANG_ROCM_SMALLM_FP8_PROJ = EnvBool(True)
+    # Quark MXFP4 dense linear on gfx95: use AITER's preshuffled ASM/CK FP4 GEMM
+    # (shared a4w4 tuned CSVs) instead of the Triton gemm_afp4wfp4 path.
+    SGLANG_ROCM_USE_AITER_FP4_ASM_GEMM = EnvBool(False)
     # Fold the KDA [f_a|b] tail into the wide [q,k,v,g] projection so the whole
     # in-proj is one GEMM. Decode is bandwidth bound there, so the 144 extra
     # output columns ride along nearly free.

SGLANG_ROCM_USE_AITER_FP4_ASM_GEMM 환경 변수가 기본값 False로 추가되었습니다. 이 변수는 gfx95 아키텍처에서만 유효하며, 활성화될 경우 Quark W4A4 MXFP4 연산에 AITER의 ASM/CK 기반 FP4 GEMM을 사용하게 됩니다. 이는 기존의 Triton 기반 gemm_afp4wfp4 경로 대신 사용됩니다.

2. AITER ASM FP4 GEMM 통합 (python/sglang/srt/layers/quantization/quark/schemes/quark_w4a4_mxfp4.py)

이 파일은 Quark W4A4 MXFP4 스키마를 위한 핵심 로직을 포함하며, AITER의 ASM/CK 기반 GEMM을 사용하기 위한 준비 및 실행 과정을 정의합니다.

주요 변경 사항:

  • ASM FP4 GEMM 지원 확인: _use_aiter_asm_fp4_gemm 변수를 통해 환경 변수 설정 및 gfx95 아키텍처 지원 여부를 확인합니다.
  • 가중치 스케일(Weight Scale) 처리:
    • _asm_fp4_scale_swizzle_supported: AITER ASM GEMM이 요구하는 가중치 스케일의 차원(행은 32의 배수, 열은 8의 배수)을 만족하는지 검사합니다.
    • _swizzle_asm_fp4_scale: AITER ASM GEMM의 타일 순서에 맞게 가중치 스케일을 변환(swizzle)합니다.
  • 활성화 스케일(Activation Scale) 처리:
    • _ASM_FP4_ACT_SCALE_ROW_MULTIPLE, _E8M0_ONE: 활성화 스케일 패딩 및 처리를 위한 상수를 정의합니다.
    • _shuffle_asm_fp4_act_scale: AITER의 per_1x32_f4_quant(shuffle=True) 커널이 요구하는 타일 레이아웃에 맞게 활성화 스케일을 패딩하고 변환합니다.
  • 입력 데이터 준비:
    • _asm_fp4_prequantized_input: 이미 양자화된 활성화 텐서 (x_fp4, x_scales)를 gemm_a4w4 커널이 요구하는 형식으로 변환합니다. 이 함수는 튜닝된 Triton 퓨전 에필로그(epilogue)를 사용하는 경우 발생하는 3개 또는 5개 튜플 입력을 지원하지 않으며, 이 경우 에러를 발생시킵니다. 이는 가중치가 미리 변환(preshuffled)되어 Triton으로의 폴백(fallback)이 불가능하기 때문입니다.
  • AITER 커널 등록:
    • aiter_per_1x32_f4_quant 및 aiter_gemm_a4w4 함수를 SGLang 커스텀 Op으로 등록합니다. torch.compile이 추적할 수 있도록 가짜 구현(fake implementation)도 함께 제공됩니다.
  • 가중치 로딩 후 처리 (process_weights_after_loading):
    • 새로운 환경 변수가 활성화되고 gfx95 아키텍처를 지원하는 경우, 가중치 스케일의 차원을 검사하고 지원되는 경우 _swizzle_asm_fp4_scale을 사용하여 변환합니다.
    • 가중치 자체도 shuffle_weight 함수를 통해 AITER ASM GEMM이 요구하는 레이아웃으로 변환됩니다.
    • 이 과정에서 layer.use_aiter_asm_fp4_gemm 플래그가 설정됩니다.
    • 만약 가중치 스케일의 차원이 지원되지 않거나, 특정 조건(예: N <= _ASM_FP4_UNSAFE_MAX_N)을 만족하지 못하면 경고 로그를 출력하고 기존 Triton 경로를 유지합니다.
  • 가중치 적용 (apply_weights):
    • layer.use_aiter_asm_fp4_gemm이 True인 경우, _asm_fp4_prequantized_input을 통해 준비된 입력과 변환된 가중치를 사용하여 gemm_a4w4 커널을 호출합니다.
    • bpreshuffle=True 옵션은 가중치 행렬 B만 미리 변환(preshuffled)됨을 의미합니다. 입력 활성화 A는 표준 FP4 형식으로 소비됩니다.
    • 바이어스(bias)가 있는 경우 GEMM 연산 후에 추가됩니다.
--- a/python/sglang/srt/layers/quantization/quark/schemes/quark_w4a4_mxfp4.py
+++ b/python/sglang/srt/layers/quantization/quark/schemes/quark_w4a4_mxfp4.py
@@ -6,6 +6,7 @@
 
 import torch
 
+from sglang.srt.environ import envs
 from sglang.srt.layers.parameter import (
     GroupQuantScaleParameter,
     ModelWeightParameter,
@@ -29,6 +30,82 @@
 
 _is_hip = is_hip()
 
+_ASM_FP4_SCALE_ROW_MULTIPLE = 32
+_ASM_FP4_SCALE_COL_MULTIPLE = 8
+# Workaround: gemm_a4w4's untuned ASM heuristic returns wrong rows 1..M-1 for
+# N <= 128 at 2 <= M <= 64 (measured on ROCm/aiter@e7d2453f2, MI355X).
+# TODO(vorapolsiloai): link the AITER issue; drop once the heuristic is fixed.
+_ASM_FP4_UNSAFE_MAX_N = 128
+
+# Keep this path opt-in while the AITER ASM integration is validated across the
+# Quark model matrix. Unlike the Triton fallback, it consumes AITER's
+# a4w4_blockscale_tuned_gemm CSVs.
+_use_aiter_asm_fp4_gemm = _is_hip and envs.SGLANG_ROCM_USE_AITER_FP4_ASM_GEMM.get()
+
+
+def _asm_fp4_scale_swizzle_supported(weight_scale: torch.Tensor) -> bool:
+    if weight_scale.ndim != 2:
+        return False
+    rows, cols = weight_scale.shape
+    return (
+        rows % _ASM_FP4_SCALE_ROW_MULTIPLE == 0
+        and cols % _ASM_FP4_SCALE_COL_MULTIPLE == 0
+    )
+
+
+def _swizzle_asm_fp4_scale(scale: torch.Tensor) -> torch.Tensor:
+    """Convert a (rows, K/32) E8M0 scale tensor to AITER ASM tile order."""
+    rows, cols = scale.shape
+    return (
+        scale.view(rows // 32, 2, 16, cols // 8, 2, 4, 1)
+        .permute(0, 3, 5, 2, 4, 1, 6)
+        .contiguous()
+        .view(rows, cols)
+    )
+
+
+# Padding of AITER's per_1x32_f4_quant(shuffle=True) activation scales.
+_ASM_FP4_ACT_SCALE_ROW_MULTIPLE = 256
+_E8M0_ONE = 0x7F
+
+
+def _shuffle_asm_fp4_act_scale(x_scales: torch.Tensor) -> torch.Tensor:
+    """Pad and swizzle row-major (M, K/32) E8M0 activation scales for gemm_a4w4."""
+    rows, cols = x_scales.shape
+    row_tile, col_tile = _ASM_FP4_ACT_SCALE_ROW_MULTIPLE, _ASM_FP4_SCALE_COL_MULTIPLE
+    # Pad with E8M0 1.0, not garbage: the kernel reads whole tiles, and 0xFF is NaN.
+    padded = torch.full(
+        (
+            (rows + row_tile - 1) // row_tile * row_tile,
+            (cols + col_tile - 1) // col_tile * col_tile,
+        ),
+        _E8M0_ONE,
+        dtype=torch.uint8,
+        device=x_scales.device,
+    )
+    padded[:rows, :cols] = x_scales.view(torch.uint8)
+    return _swizzle_asm_fp4_scale(padded).view(torch.float8_e8m0fnu)
+
+
+def _asm_fp4_prequantized_input(
+    x: tuple[torch.Tensor, ...],
+) -> tuple[torch.Tensor, torch.Tensor]:
+    """Adapt an already-quantized ``(x_fp4, x_scales)`` activation for gemm_a4w4."""
+    # The 3- and 5-tuples request Triton-only fused epilogues (atomic pre-quant,
+    # split + cat); weights are already preshuffled, so there is no fallback.
+    if len(x) != 2:
+        raise NotImplementedError(
+            "AITER ASM FP4 GEMM only supports the (x_fp4, x_scales) tuple input; "
+            f"got a {len(x)}-tuple. Disable SGLANG_ROCM_USE_AITER_FP4_ASM_GEMM "
+            "for models that use this projection."
+        )
+    x_q, x_scales = x
+    # Only B is preshuffled (bpreshuffle=True), so the packed FP4 activation is
+    # consumed as-is. The scales come from the Triton fused-quant kernels in
+    # row-major (M, K/32) order, which gemm_a4w4 cannot read directly.
+    return x_q.view(torch.float4_e2m1fn_x2), _shuffle_asm_fp4_act_scale(x_scales)
+
+
 # On GPUs that lack the fp4-activation WMMA scale instruction
 # (V_WMMA_SCALE_F32_32X16X128_F4, e.g. gfx1250) the a4w4 (fp4 x fp4) linear GEMM
 # cannot run. The MoE path is switched to a8w4 via AITER_FORCE_A8W4=1 (handled
@@ -83,6 +160,73 @@
 def _dequant_mxfp4_to_bf16(
 
 
+from aiter import gemm_a4w4 as _gemm_a4w4_orig
+from aiter import per_1x32_f4_quant_hip as _per_1x32_f4_quant_hip_orig
+from aiter.ops.shuffle import shuffle_weight
 from aiter.ops.triton.gemm.fused.fused_gemm_afp4wfp4_split_cat import (
     fused_gemm_afp4wfp4_split_cat as _fused_gemm_afp4wfp4_split_cat_orig,
 )
@@ -171,6 +245,73 @@
     def dynamic_mxfp4_quant(x):
         return torch.ops.sglang.aiter_dynamic_mxfp4_quant(x)
 
+    def _aiter_per_1x32_f4_quant(
+        x: torch.Tensor,
+    ) -> tuple[torch.Tensor, torch.Tensor]:
+        return _per_1x32_f4_quant_hip_orig(x, shuffle=True)
+
+    def _aiter_per_1x32_f4_quant_fake(
+        x: torch.Tensor,
+    ) -> tuple[torch.Tensor, torch.Tensor]:
+        rows, cols = x.shape
+        x_fp4 = torch.empty((rows, cols // 2), dtype=torch.uint8, device=x.device)
+        scales = torch.empty(
+            (rows, (cols + 31) // 32), dtype=torch.uint8, device=x.device
+        )
+        return x_fp4, scales
+
+    direct_register_custom_op(
+        op_name="aiter_per_1x32_f4_quant",
+        op_func=_aiter_per_1x32_f4_quant,
+        mutates_args=[],
+        fake_impl=_aiter_per_1x32_f4_quant_fake,
+    )
+
+    def per_1x32_f4_quant(
+        x: torch.Tensor,
+    ) -> tuple[torch.Tensor, torch.Tensor]:
+        return torch.ops.sglang.aiter_per_1x32_f4_quant(x)
+
+    def _aiter_gemm_a4w4(
+        x: torch.Tensor,
+        w: torch.Tensor,
+        x_scales: torch.Tensor,
+        w_scales: torch.Tensor,
+        output_dtype_ref: torch.Tensor,
+    ) -> torch.Tensor:
+        return _gemm_a4w4_orig(
+            x,
+            w.view(x.dtype), # Cast weight to input activation's dtype
+            x_scales,
+            w_scales.view(x_scales.dtype), # Cast weight scales to input scales' dtype
+            dtype=output_dtype_ref.dtype,
+            bpreshuffle=True,
+        )
+
+    def _aiter_gemm_a4w4_fake(
+        x: torch.Tensor,
+        w: torch.Tensor,
+        x_scales: torch.Tensor,
+        w_scales: torch.Tensor,
+        output_dtype_ref: torch.Tensor,
+    ) -> torch.Tensor:
+        del x_scales, w_scales
+        return torch.empty(
+            (x.shape[0], w.shape[0]),
+            dtype=output_dtype_ref.dtype,
+            device=x.device,
+        )
+
+    direct_register_custom_op(
+        op_name="aiter_gemm_a4w4",
+        op_func=_aiter_gemm_a4w4,
+        mutates_args=[],
+        fake_impl=_aiter_gemm_a4w4_fake,
+    )
+
+    def gemm_a4w4(
+        x: torch.Tensor,
+        w: torch.Tensor,
+        x_scales: torch.Tensor,
+        w_scales: torch.Tensor,
+        output_dtype_ref: torch.Tensor,
+    ) -> torch.Tensor:
+        return torch.ops.sglang.aiter_gemm_a4w4(
+            x, w, x_scales, w_scales, output_dtype_ref
+        )
+
     def _aiter_fused_gemm_split_cat(
         x: torch.Tensor,
         w: torch.Tensor,
@@ -270,6 +417,41 @@
             # FP4 block scales are folded into the bf16 weight; drop them.
             layer.weight_scale = None
             layer.dequantized_bf16 = True
+            return
+
+        layer.use_aiter_asm_fp4_gemm = False
+        if not _use_aiter_asm_fp4_gemm or not is_gfx95_supported():
+            return
+
+        if not _asm_fp4_scale_swizzle_supported(layer.weight_scale.data):
+            logger.warning_once(
+                "AITER ASM FP4 GEMM requires weight-scale dimensions divisible "
+                "by (%d, %d), but this layer has shape %s. Falling back to the "
+                "AITER Triton FP4 GEMM for this layer.",
+                _ASM_FP4_SCALE_ROW_MULTIPLE,
+                _ASM_FP4_SCALE_COL_MULTIPLE,
+                tuple(layer.weight_scale.shape),
+            )
+            return
+
+        if layer.weight.shape[0] <= _ASM_FP4_UNSAFE_MAX_N:
+            logger.warning_once(
+                "AITER ASM FP4 GEMM is unreliable for N <= %d; keeping the AITER "
+                "Triton FP4 GEMM for a layer with N=%d.",
+                _ASM_FP4_UNSAFE_MAX_N,
+                layer.weight.shape[0],
+            )
+            return
+
+        layer.weight_scale = torch.nn.Parameter(
+            _swizzle_asm_fp4_scale(layer.weight_scale.data),
+            requires_grad=False,
+        )
+        layer.weight = torch.nn.Parameter(
+            shuffle_weight(layer.weight.data, layout=(16, 16)),
+            requires_grad=False,
+        )
+        layer.use_aiter_asm_fp4_gemm = True
 
     def create_weights(
         self,
@@ -689,6 +871,41 @@
                 x = x[0]
             return torch.nn.functional.linear(x, layer.weight, bias)
 
+        if getattr(layer, "use_aiter_asm_fp4_gemm", False):
+            output_shape = None
+            if isinstance(x, tuple):
+                # Input is already quantized, e.g. from fused RMSNorm
+                x_q, x_scales = _asm_fp4_prequantized_input(x)
+            else:
+                # Input needs quantization
+                if x.dim() == 3:
+                    output_shape = [*x.shape[:-1], layer.weight.shape[0]]
+                    x = x.view(-1, x.shape[-1])
+                # Quantize activations and shuffle scales for gemm_a4w4
+                x_q, x_scales = per_1x32_f4_quant(x)
+
+            # Reference tensor to infer output dtype
+            output_dtype_ref = torch.empty(0, dtype=self.out_dtype, device=x_q.device)
+            y = gemm_a4w4(
+                x_q,
+                layer.weight,
+                x_scales,
+                layer.weight_scale,
+                output_dtype_ref,
+            )
+            if bias is not None:
+                y = y + bias
+            return y.view(*output_shape) if output_shape is not None else y
+
         # Bias will be added after the GEMM if provided
         three_d = False
         fused_gemm_split_cat = False

3. CPU Unit 테스트 (test/registered/unit/layers/quantization/test_quark_mxfp4_asm.py)

이 파일은 AITER ASM FP4 GEMM 레이아웃 관련 헬퍼 함수들의 정확성을 검증하기 위한 CPU 전용 테스트를 포함합니다.

--- /dev/null
+++ b/test/registered/unit/layers/quantization/test_quark_mxfp4_asm.py
@@ -0,0 +1,121 @@
+"""CPU-only tests for Quark MXFP4 AITER ASM layout helpers."""
+
+from sglang.test.ci.ci_register import register_cpu_ci
+
+register_cpu_ci(est_time=5, suite="base-a-test-cpu")
+
+import unittest
+from unittest import mock
+
+import torch
+
+from sglang.srt.layers.quantization.quark.schemes import quark_w4a4_mxfp4
+from sglang.srt.layers.quantization.quark.schemes.quark_w4a4_mxfp4 import (
+    _asm_fp4_prequantized_input,
+    _asm_fp4_scale_swizzle_supported,
+    _shuffle_asm_fp4_act_scale,
+    _swizzle_asm_fp4_scale,
+)
+from sglang.test.test_utils import CustomTestCase
+
+
+class TestQuarkMxfp4AsmScaleLayout(CustomTestCase):
+    def test_supported_shape(self):
+        scale = torch.empty((32, 8), dtype=torch.uint8)
+        self.assertTrue(_asm_fp4_scale_swizzle_supported(scale))
+
+    def test_rejects_unsupported_shapes(self):
+        self.assertFalse(
+            _asm_fp4_scale_swizzle_supported(torch.empty((16, 8), dtype=torch.uint8))
+        )
+        self.assertFalse(
+            _asm_fp4_scale_swizzle_supported(torch.empty((32, 4), dtype=torch.uint8))
+        )
+        self.assertFalse(
+            _asm_fp4_scale_swizzle_supported(torch.empty((1, 32, 8), dtype=torch.uint8))
+        )
+
+    def test_swizzle_matches_aiter_tile_permutation(self):
+        scale = torch.arange(32 * 8, dtype=torch.int32).view(32, 8)
+        expected = (
+            scale.view(1, 2, 16, 1, 2, 4, 1)
+            .permute(0, 3, 5, 2, 4, 1, 6)
+            .contiguous()
+            .view(32, 8)
+        )
+  
+```

이 테스트들은 가중치 스케일의 차원 지원 여부와 AITER의 타일 순서에 맞춘 변환 로직을 검증합니다.

## 왜 이게 좋은가?

### 1. 성능 향상

이 PR의 핵심 목표는 AMD GPU에서 Quark 모델의 FP4 GEMM 연산 성능을 향상시키는 것입니다. AITER의 `gemm_a4w4` 커널은 모델별로 튜닝된 CSV 테이블을 활용할 수 있어, 기존 Triton 기반 커널보다 더 높은 성능을 제공합니다.

리뷰 댓글에 따르면, Qwen3.8-27B 모델을 AMD Instinct MI355X에서 테스트한 결과 다음과 같은 성능 향상이 관찰되었습니다:

*   **처리량(Throughput):** 평균 **8.15%** 향상
*   **TTFT (Time To First Token):** 평균 **4.91%** 감소
*   **TPOT (Time Per Output Token):** 평균 **8.05%** 감소

이러한 수치는 새로운 AITER ASM FP4 GEMM 경로가 실제 워크로드에서 상당한 성능 개선을 가져옴을 보여줍니다.

### 2. 튜닝 테이블 공유 및 관리 효율성

기존에는 SGLang이 Quark 모델에 대한 별도의 Triton 튜닝 테이블을 관리해야 했습니다. 이 PR을 통해 AITER 및 vLLM과 동일한 `a4w4_blockscale_tuned_gemm.csv` 튜닝 테이블을 공유하게 되면서, 튜닝 테이블 관리의 복잡성이 줄어들고 일관성이 향상되었습니다.

### 3. 정확성 보장

성능 향상과 더불어 정확성도 중요하게 고려되었습니다. Accuracy 테스트 결과, GSM8K 데이터셋에서 기존 Baseline과 새로운 ASM FP4 경로 간의 정확도 차이가 없었으며 (0.955), 이는 성능 개선이 모델의 정확성에 부정적인 영향을 미치지 않음을 확인시켜 줍니다.

### 4. 일반적인 교훈

*   **벤더별 최적화 활용:** 특정 하드웨어(여기서는 AMD GPU)의 최신 라이브러리 및 커널(AITER ASM/CK)을 적극적으로 활용하여 성능을 극대화할 수 있습니다.
*   **옵트인(Opt-in) 전략:** 새로운 기능이나 성능 개선 경로를 도입할 때, 초기에는 옵트인 방식으로 제공하여 안정성을 확보하고 점진적으로 확대하는 것이 좋은 전략입니다. 이는 기존 워크로드에 미치는 영향을 최소화하면서 새로운 기능을 검증할 기회를 제공합니다.
*   **커스텀 Op 등록 및 `torch.compile` 호환성:** PyTorch의 `torch.compile`과 같은 컴파일러를 활용하기 위해, 커스텀 연산을 Op으로 등록하고 적절한 가짜 구현(fake implementation)을 제공하는 것이 중요합니다.
*   **정확성 검증의 중요성:** 성능 최적화 과정에서 정확성 저하가 발생하지 않도록 철저한 검증이 필수적입니다.

## 리뷰 피드백 및 논의

리뷰 과정에서 몇 가지 중요한 논의가 있었습니다:

*   **환경 변수 vs. 기본 활성화:** `SGLANG_ROCM_USE_AITER_FP4_ASM_GEMM` 환경 변수를 계속 유지할 것인지, 아니면 `gfx95`에서 `SGLANG_USE_AITER=1` 설정 시 기본적으로 AITER CK/ASM을 사용하도록 할 것인지에 대한 논의가 있었습니다. 현재 PR은 옵트인 방식을 채택하여 위험을 최소화했습니다. (참고: 이후 후속 PR에서 `--fp4-gemm-backend` 옵션으로 관리하는 방안이 제안되었습니다.)
*   **vLLM과의 차이점:** vLLM은 특정 조건에서만 Triton으로 폴백하는 반면, SGLang은 기존 Triton 튜닝 테이블을 유지해야 하므로 기본 활성화 시 주의가 필요하다는 점이 지적되었습니다. 이 PR은 이러한 차이점을 고려하여 옵트인 방식을 선택했습니다.
*   **코드 수정 제안:** `apply_weights` 함수 내에서 입력이 튜플(`x`)일 경우 `per_1x32_f4_quant` 호출을 건너뛰도록 하는 제안이 있었습니다. 이는 이미 양자화된 입력에 대해 불필요한 연산을 방지하기 위함이며, 후속 커밋에서 반영되었습니다.
*   **CI 문제:** PR 병합 과정에서 NVIDIA 및 AMD CI의 불안정성으로 인해 지연이 발생했으나, 최종적으로는 해당 문제들이 해결되어 PR이 성공적으로 병합되었습니다.

## References

*   [AITER `gemm_a4w4` documentation](https://github.com/ROCmSoftwarePlatform/AITER/blob/main/docs/ops/gemm.md#gemm_a4w4)
*   [torch.compile documentation](https://pytorch.org/docs/stable/torch.compile.html)
*   [SGLang Environment Variables](https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/environ.py)
*   [AITER Triton Kernels](https://github.com/ROCmSoftwarePlatform/AITER/tree/main/aiter/ops/triton/gemm/fused)


## 참고 자료
- https://github.com/ROCmSoftwarePlatform/AITER/blob/main/docs/ops/gemm.md#gemm_a4w4
- https://pytorch.org/docs/stable/torch.compile.html
- https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/environ.py
- https://github.com/ROCmSoftwarePlatform/AITER/tree/main/aiter/ops/triton/gemm/fused

> ⚠️ **알림:** 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.

댓글

관련 포스트

PR Analysis 의 다른글