Skip to content

Commit fa5fc74

Browse files
authored
[cuda] GGUF Q6_K real packed INT6 (W6A8 dp4a) + GGUF CI export (#20229)
Add a genuine 6-bit packed weight path for GGUF Q6_K on the CUDA backend, parallel to the int4/int8 plain_mm paths: - int6_plain_mm CUDA shim (W6A8 dp4a; ql/qh planes; spread2; -32 symmetric offset) - CudaPackedInt6Tensor (ql/qh + per-group bf16 scale; symmetric, no zero tensor) - int6_dispatch: F.linear routing (M<=4 -> executorch_cuda::int6_plain_mm op, M>4 -> dequant) - backend fallback-kernel + custom_ops_to_c_shims registration; CMake build - route GGUF Q6_K -> CudaPackedInt6Tensor (gguf_loader, pack_cuda, dequantize_weight) - tests: int6 gtest, test_int6_dispatch.py, pack round-trip; fix stale int4/int6 type asserts CI (export_model_artifact.sh, gemma4_31b): download the Q4_K_M GGUF from unsloth/gemma-4-31B-it-GGUF (tokenizer from unsloth/gemma-4-31B-it) and run the inference sanity check + export via the GGUF loader (--gguf) instead of the prequantized HF checkpoint. prompt(p) | prefill tok/s | decode tok/s -- | -- | -- p=2043, d =128 | 2280.1 | 46.73 --------- Signed-off-by: gasoonjia <gasoonjia@icloud.com>
1 parent 218cc45 commit fa5fc74

20 files changed

Lines changed: 1752 additions & 48 deletions

.ci/scripts/export_model_artifact.sh

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ case "$HF_MODEL" in
195195
PREPROCESSOR_FEATURE_SIZE=""
196196
PREPROCESSOR_OUTPUT=""
197197
;;
198-
SocialLocalMobile/gemma-4-31B-it-HQQ-INT4)
198+
unsloth/gemma-4-31B-it-GGUF)
199199
MODEL_NAME="gemma4_31b"
200200
TASK=""
201201
MAX_SEQ_LEN=""
@@ -205,7 +205,7 @@ case "$HF_MODEL" in
205205
;;
206206
*)
207207
echo "Error: Unsupported model '$HF_MODEL'"
208-
echo "Supported models: mistralai/Voxtral-Mini-3B-2507, mistralai/Voxtral-Mini-4B-Realtime-2602, openai/whisper-{small, medium, large, large-v2, large-v3, large-v3-turbo}, google/gemma-3-4b-it, Qwen/Qwen3-0.6B, nvidia/diar_streaming_sortformer_4spk-v2, nvidia/parakeet-tdt, facebook/dinov2-small-imagenet1k-1-layer, SocialLocalMobile/Qwen3.5-35B-A3B-HQQ-INT4, SocialLocalMobile/gemma-4-31B-it-HQQ-INT4"
208+
echo "Supported models: mistralai/Voxtral-Mini-3B-2507, mistralai/Voxtral-Mini-4B-Realtime-2602, openai/whisper-{small, medium, large, large-v2, large-v3, large-v3-turbo}, google/gemma-3-4b-it, Qwen/Qwen3-0.6B, nvidia/diar_streaming_sortformer_4spk-v2, nvidia/parakeet-tdt, facebook/dinov2-small-imagenet1k-1-layer, SocialLocalMobile/Qwen3.5-35B-A3B-HQQ-INT4, unsloth/gemma-4-31B-it-GGUF"
209209
exit 1
210210
;;
211211
esac
@@ -469,22 +469,28 @@ if [ "$MODEL_NAME" = "qwen3_5_moe" ]; then
469469
exit 0
470470
fi
471471

472-
# Gemma 4 31B uses a prequantized checkpoint and custom export script
472+
# Gemma 4 31B: download the Q4_K_M GGUF and export via the GGUF loader
473473
if [ "$MODEL_NAME" = "gemma4_31b" ]; then
474474
pip install safetensors huggingface_hub gguf
475475

476-
# Download prequantized model outside OUTPUT_DIR to avoid uploading on failure
476+
# Download GGUF + tokenizer outside OUTPUT_DIR to avoid uploading on failure.
477+
# The unsloth GGUF repo ships the .gguf but no tokenizer.json, so the tokenizer
478+
# is fetched from the (non-GGUF) unsloth/gemma-4-31B-it repo.
477479
LOCAL_MODEL_DIR=$(mktemp -d)
478480
INDUCTOR_CACHE=$(mktemp -d "${RUNNER_TEMP:-/tmp}/inductor_cache_XXXXXX")
479481
INDUCTOR_TMPDIR=$(mktemp -d "${RUNNER_TEMP:-/tmp}/tmpdir_XXXXXX")
480482
trap 'rm -rf "$LOCAL_MODEL_DIR" "$INDUCTOR_CACHE" "$INDUCTOR_TMPDIR"' EXIT
481483

482-
python -c "from huggingface_hub import snapshot_download; snapshot_download('${HF_MODEL}', local_dir='${LOCAL_MODEL_DIR}')"
484+
GGUF_FILE="gemma-4-31B-it-Q4_K_M.gguf"
485+
python -c "from huggingface_hub import hf_hub_download; hf_hub_download('unsloth/gemma-4-31B-it-GGUF', '${GGUF_FILE}', local_dir='${LOCAL_MODEL_DIR}')"
486+
python -c "from huggingface_hub import hf_hub_download; hf_hub_download('unsloth/gemma-4-31B-it', 'tokenizer.json', local_dir='${LOCAL_MODEL_DIR}')"
487+
GGUF_PATH="${LOCAL_MODEL_DIR}/${GGUF_FILE}"
483488

484-
# Sanity check: run inference on the prequantized model
489+
# Sanity check: run inference on the GGUF model
485490
echo "::group::Inference sanity check"
486491
INFERENCE_OUTPUT=$(python -m executorch.examples.models.gemma4_31b.inference \
487-
--prequantized "$LOCAL_MODEL_DIR" \
492+
--gguf "$GGUF_PATH" \
493+
--tokenizer-path "${LOCAL_MODEL_DIR}/tokenizer.json" \
488494
--prompt "What is the capital of France?" \
489495
--max-new-tokens 32 \
490496
--temperature 0 \
@@ -497,14 +503,14 @@ if [ "$MODEL_NAME" = "gemma4_31b" ]; then
497503
echo "::endgroup::"
498504

499505
# Copy tokenizer for the runner
500-
cp "$LOCAL_MODEL_DIR/tokenizer.json" "${OUTPUT_DIR}/tokenizer.json"
506+
cp "${LOCAL_MODEL_DIR}/tokenizer.json" "${OUTPUT_DIR}/tokenizer.json"
501507

502508
# Export to .pte/.ptd (short cache dir avoids objcopy symbol length issues)
503509
echo "::group::Export"
504510
TMPDIR="$INDUCTOR_TMPDIR" \
505511
TORCHINDUCTOR_CACHE_DIR="$INDUCTOR_CACHE" \
506512
python -m executorch.examples.models.gemma4_31b.export \
507-
--prequantized "$LOCAL_MODEL_DIR" \
513+
--gguf "$GGUF_PATH" \
508514
--output-dir "${OUTPUT_DIR}"
509515
echo "::endgroup::"
510516

.ci/scripts/test_model_e2e.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ case "$HF_MODEL" in
228228
AUDIO_FILE=""
229229
IMAGE_PATH=""
230230
;;
231-
SocialLocalMobile/gemma-4-31B-it-HQQ-INT4)
231+
unsloth/gemma-4-31B-it-GGUF)
232232
MODEL_NAME="gemma4_31b"
233233
RUNNER_TARGET="gemma4_31b_runner"
234234
RUNNER_PATH="gemma4_31b"
@@ -242,7 +242,7 @@ case "$HF_MODEL" in
242242
;;
243243
*)
244244
echo "Error: Unsupported model '$HF_MODEL'"
245-
echo "Supported models: mistralai/Voxtral-Mini-3B-2507, mistralai/Voxtral-Mini-4B-Realtime-2602, nvidia/diar_streaming_sortformer_4spk-v2, openai/whisper series (whisper-{small, medium, large, large-v2, large-v3, large-v3-turbo}), google/gemma-3-4b-it, Qwen/Qwen3-0.6B, nvidia/parakeet-tdt, facebook/dinov2-small-imagenet1k-1-layer, SocialLocalMobile/Qwen3.5-35B-A3B-HQQ-INT4, SocialLocalMobile/gemma-4-31B-it-HQQ-INT4"
245+
echo "Supported models: mistralai/Voxtral-Mini-3B-2507, mistralai/Voxtral-Mini-4B-Realtime-2602, nvidia/diar_streaming_sortformer_4spk-v2, openai/whisper series (whisper-{small, medium, large, large-v2, large-v3, large-v3-turbo}), google/gemma-3-4b-it, Qwen/Qwen3-0.6B, nvidia/parakeet-tdt, facebook/dinov2-small-imagenet1k-1-layer, SocialLocalMobile/Qwen3.5-35B-A3B-HQQ-INT4, unsloth/gemma-4-31B-it-GGUF"
246246
exit 1
247247
;;
248248
esac

.github/workflows/cuda.yml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,8 @@ jobs:
258258
name: "dinov2-small-imagenet1k-1-layer"
259259
- repo: "SocialLocalMobile"
260260
name: "Qwen3.5-35B-A3B-HQQ-INT4"
261-
- repo: "SocialLocalMobile"
262-
name: "gemma-4-31B-it-HQQ-INT4"
261+
- repo: "unsloth"
262+
name: "gemma-4-31B-it-GGUF"
263263
quant:
264264
- "non-quantized"
265265
- "quantized-int4-tile-packed"
@@ -281,12 +281,12 @@ jobs:
281281
quant: "quantized-int4-weight-only"
282282
# Gemma 4 31B uses a prequantized checkpoint, only tile-packed
283283
- model:
284-
repo: "SocialLocalMobile"
285-
name: "gemma-4-31B-it-HQQ-INT4"
284+
repo: "unsloth"
285+
name: "gemma-4-31B-it-GGUF"
286286
quant: "non-quantized"
287287
- model:
288-
repo: "SocialLocalMobile"
289-
name: "gemma-4-31B-it-HQQ-INT4"
288+
repo: "unsloth"
289+
name: "gemma-4-31B-it-GGUF"
290290
quant: "quantized-int4-weight-only"
291291
# Voxtral Realtime only supports int4-tile-packed on CUDA
292292
- model:
@@ -342,7 +342,7 @@ jobs:
342342
with:
343343
timeout: 150
344344
secrets-env: EXECUTORCH_HF_TOKEN
345-
runner: ${{ (matrix.model.name == 'Qwen3.5-35B-A3B-HQQ-INT4' || matrix.model.name == 'gemma-4-31B-it-HQQ-INT4') && 'mt-l-x86iavx512-11-125-a100' || 'mt-l-x86aavx2-29-113-a10g' }}
345+
runner: ${{ (matrix.model.name == 'Qwen3.5-35B-A3B-HQQ-INT4' || matrix.model.name == 'gemma-4-31B-it-GGUF') && 'mt-l-x86iavx512-11-125-a100' || 'mt-l-x86aavx2-29-113-a10g' }}
346346
gpu-arch-type: cuda
347347
gpu-arch-version: "13.0"
348348
use-custom-docker-registry: false
@@ -424,8 +424,8 @@ jobs:
424424
name: "dinov2-small-imagenet1k-1-layer"
425425
- repo: "SocialLocalMobile"
426426
name: "Qwen3.5-35B-A3B-HQQ-INT4"
427-
- repo: "SocialLocalMobile"
428-
name: "gemma-4-31B-it-HQQ-INT4"
427+
- repo: "unsloth"
428+
name: "gemma-4-31B-it-GGUF"
429429
quant:
430430
- "non-quantized"
431431
- "quantized-int4-tile-packed"
@@ -447,12 +447,12 @@ jobs:
447447
quant: "quantized-int4-weight-only"
448448
# Gemma 4 31B uses a prequantized checkpoint, only tile-packed
449449
- model:
450-
repo: "SocialLocalMobile"
451-
name: "gemma-4-31B-it-HQQ-INT4"
450+
repo: "unsloth"
451+
name: "gemma-4-31B-it-GGUF"
452452
quant: "non-quantized"
453453
- model:
454-
repo: "SocialLocalMobile"
455-
name: "gemma-4-31B-it-HQQ-INT4"
454+
repo: "unsloth"
455+
name: "gemma-4-31B-it-GGUF"
456456
quant: "quantized-int4-weight-only"
457457
# Voxtral Realtime only supports int4-tile-packed on CUDA
458458
- model:
@@ -502,7 +502,7 @@ jobs:
502502
quant: "non-quantized"
503503
with:
504504
timeout: 90
505-
runner: ${{ (matrix.model.name == 'Qwen3.5-35B-A3B-HQQ-INT4' || matrix.model.name == 'gemma-4-31B-it-HQQ-INT4') && 'mt-l-x86iavx512-11-125-a100' || 'mt-l-x86aavx2-29-113-a10g' }}
505+
runner: ${{ (matrix.model.name == 'Qwen3.5-35B-A3B-HQQ-INT4' || matrix.model.name == 'gemma-4-31B-it-GGUF') && 'mt-l-x86iavx512-11-125-a100' || 'mt-l-x86aavx2-29-113-a10g' }}
506506
gpu-arch-type: cuda
507507
gpu-arch-version: "13.0"
508508
use-custom-docker-registry: false

backends/cuda/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ if(CMAKE_CUDA_COMPILER)
114114
_aoti_cuda_shim_sources
115115
runtime/shims/int4mm.cu
116116
runtime/shims/int4_plain_mm.cu
117+
runtime/shims/int6_plain_mm.cu
117118
runtime/shims/int8_plain_mm.cu
118119
runtime/shims/sort.cu
119120
runtime/shims/rand.cu

backends/cuda/cuda_backend.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@ def get_supported_fallback_kernels(cls) -> Dict[str, Any]:
231231
"aoti_torch_cuda_randint_low_out": None,
232232
"executorch_cuda::int4_plain_mm": None,
233233
"aoti_torch_cuda_int4_plain_mm": None,
234+
"executorch_cuda::int6_plain_mm": None,
235+
"aoti_torch_cuda_int6_plain_mm": None,
234236
"executorch_cuda::int8_plain_mm": None,
235237
"aoti_torch_cuda_int8_plain_mm": None,
236238
}
@@ -314,6 +316,11 @@ def get_aoti_compile_options(
314316
"AtenTensorHandle, AtenTensorHandle, AtenTensorHandle, "
315317
"AtenTensorHandle, int64_t, AtenTensorHandle*)"
316318
],
319+
torch.ops.executorch_cuda.int6_plain_mm.default: [
320+
"AOTITorchError aoti_torch_cuda_int6_plain_mm("
321+
"AtenTensorHandle, AtenTensorHandle, AtenTensorHandle, "
322+
"AtenTensorHandle, int64_t, AtenTensorHandle*)"
323+
],
317324
torch.ops.executorch_cuda.int8_plain_mm.default: [
318325
"AOTITorchError aoti_torch_cuda_int8_plain_mm("
319326
"AtenTensorHandle, AtenTensorHandle, AtenTensorHandle, "

0 commit comments

Comments
 (0)