[Feature][Humming] Humming feature integration (#56685)
Signed-off-by: jinzhen.ljz <jinzhen.ljz@antgroup.com> Signed-off-by: mgoin <mgoin64@gmail.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: mgoin <mgoin64@gmail.com>
This commit is contained in:
co-authored by
Codex
mgoin
parent
27757dde02
commit
7d99c2c4fd
@@ -347,47 +347,67 @@ steps:
|
||||
commands:
|
||||
- pytest -s -v evals/gsm8k/test_gsm8k_correctness.py --config-list-file=evals/gsm8k/configs/moe-refactor-dp-ep/config-b200.txt
|
||||
|
||||
- label: ":nvidia: (A100) LM Eval Humming FP16 TEMPORARY Shard %N"
|
||||
key: lm-eval-humming-f16-a100
|
||||
- label: ":nvidia: (L4) LM Eval Humming TEMPORARY"
|
||||
key: lm-eval-humming-l4
|
||||
optional: true
|
||||
timeout_in_minutes: 90
|
||||
device: l4
|
||||
num_devices: 1
|
||||
source_file_dependencies:
|
||||
- .buildkite/test_areas/lm_eval.yaml
|
||||
- tests/evals/gsm8k/configs/humming/
|
||||
- requirements/cuda.txt
|
||||
- vllm/utils/humming.py
|
||||
- vllm/model_executor/layers/quantization/humming.py
|
||||
- vllm/model_executor/layers/quantization/utils/humming/
|
||||
- vllm/model_executor/layers/fused_moe/experts/fused_humming_moe.py
|
||||
- vllm/model_executor/layers/fused_moe/oracle/
|
||||
- vllm/model_executor/kernels/linear/
|
||||
commands:
|
||||
- pytest -s -v evals/gsm8k/test_gsm8k_correctness.py --config-list-file=evals/gsm8k/configs/humming/config-l4.txt
|
||||
|
||||
- label: ":nvidia: (A100) LM Eval Humming Default TEMPORARY Shard %N"
|
||||
key: lm-eval-humming-a100-default
|
||||
optional: true
|
||||
timeout_in_minutes: 75
|
||||
device: a100
|
||||
optional: true
|
||||
num_devices: 1
|
||||
parallelism: 3
|
||||
source_file_dependencies:
|
||||
- vllm/model_executor/layers/quantization/humming.py
|
||||
- vllm/model_executor/layers/quantization/utils/humming_utils.py
|
||||
- vllm/model_executor/layers/quantization/utils/humming/
|
||||
- vllm/model_executor/layers/fused_moe/experts/fused_humming_moe.py
|
||||
- vllm/model_executor/layers/fused_moe/oracle/
|
||||
- vllm/model_executor/kernels/linear/
|
||||
commands:
|
||||
- pytest -s -v evals/gsm8k/test_gsm8k_correctness.py --config-list-file=evals/gsm8k/configs/humming/config-a100-shard-$$BUILDKITE_PARALLEL_JOB.txt
|
||||
|
||||
- label: ":nvidia: (A100) LM Eval Humming Activation INT8 TEMPORARY"
|
||||
key: lm-eval-humming-act-a100
|
||||
- label: ":nvidia: (A100) LM Eval Humming Activation INT8 TEMPORARY Shard %N"
|
||||
key: lm-eval-humming-a100-act
|
||||
optional: true
|
||||
timeout_in_minutes: 45
|
||||
device: a100
|
||||
optional: true
|
||||
num_devices: 1
|
||||
parallelism: 2
|
||||
source_file_dependencies:
|
||||
- vllm/model_executor/layers/quantization/humming.py
|
||||
- vllm/model_executor/layers/quantization/utils/humming_utils.py
|
||||
- vllm/model_executor/layers/quantization/utils/humming/
|
||||
- vllm/model_executor/layers/fused_moe/experts/fused_humming_moe.py
|
||||
- vllm/model_executor/layers/fused_moe/oracle/
|
||||
- vllm/model_executor/kernels/linear/
|
||||
commands:
|
||||
- pytest -s -v evals/gsm8k/test_gsm8k_correctness.py --config-list-file=evals/gsm8k/configs/humming/config-act-int8.txt
|
||||
- pytest -s -v evals/gsm8k/test_gsm8k_correctness.py --config-list-file=evals/gsm8k/configs/humming/config-a100-act-int8-shard-$$BUILDKITE_PARALLEL_JOB.txt
|
||||
|
||||
- label: ":nvidia: (H100) LM Eval Humming FP16 TEMPORARY Shard %N"
|
||||
key: lm-eval-humming-f16-h100
|
||||
- label: ":nvidia: (H100) LM Eval Humming Default TEMPORARY Shard %N"
|
||||
key: lm-eval-humming-h100-default
|
||||
optional: true
|
||||
timeout_in_minutes: 70
|
||||
device: h100
|
||||
optional: true
|
||||
num_devices: 1
|
||||
parallelism: 3
|
||||
source_file_dependencies:
|
||||
- vllm/model_executor/layers/quantization/humming.py
|
||||
- vllm/model_executor/layers/quantization/utils/humming_utils.py
|
||||
- vllm/model_executor/layers/quantization/utils/humming/
|
||||
- vllm/model_executor/layers/fused_moe/experts/fused_humming_moe.py
|
||||
- vllm/model_executor/layers/fused_moe/oracle/
|
||||
- vllm/model_executor/kernels/linear/
|
||||
@@ -395,14 +415,14 @@ steps:
|
||||
- pytest -s -v evals/gsm8k/test_gsm8k_correctness.py --config-list-file=evals/gsm8k/configs/humming/config-h100-shard-$$BUILDKITE_PARALLEL_JOB.txt
|
||||
|
||||
- label: ":nvidia: (H100) LM Eval Humming Activation FP8/INT8 TEMPORARY"
|
||||
key: lm-eval-humming-act-h100
|
||||
key: lm-eval-humming-h100-act
|
||||
optional: true
|
||||
timeout_in_minutes: 70
|
||||
device: h100
|
||||
optional: true
|
||||
num_devices: 1
|
||||
source_file_dependencies:
|
||||
- vllm/model_executor/layers/quantization/humming.py
|
||||
- vllm/model_executor/layers/quantization/utils/humming_utils.py
|
||||
- vllm/model_executor/layers/quantization/utils/humming/
|
||||
- vllm/model_executor/layers/fused_moe/experts/fused_humming_moe.py
|
||||
- vllm/model_executor/layers/fused_moe/oracle/
|
||||
- vllm/model_executor/kernels/linear/
|
||||
@@ -410,15 +430,35 @@ steps:
|
||||
- pytest -s -v evals/gsm8k/test_gsm8k_correctness.py --config-list-file=evals/gsm8k/configs/humming/config-act-fp8.txt
|
||||
# - pytest -s -v evals/gsm8k/test_gsm8k_correctness.py --config-list-file=evals/gsm8k/configs/humming/config-act-int8.txt
|
||||
|
||||
- label: ":nvidia: (B200) LM Eval Humming FP16 TEMPORARY"
|
||||
key: lm-eval-humming-f16-b200
|
||||
- label: ":nvidia: (H100) LM Eval Humming DeepEP TEMPORARY"
|
||||
key: lm-eval-humming-h100-deepep
|
||||
optional: true
|
||||
timeout_in_minutes: 60
|
||||
device: h100
|
||||
num_devices: 2
|
||||
source_file_dependencies:
|
||||
- .buildkite/test_areas/lm_eval.yaml
|
||||
- tests/evals/gsm8k/configs/humming/
|
||||
- requirements/cuda.txt
|
||||
- vllm/utils/humming.py
|
||||
- vllm/model_executor/layers/quantization/humming.py
|
||||
- vllm/model_executor/layers/quantization/utils/humming/
|
||||
- vllm/model_executor/layers/fused_moe/
|
||||
- vllm/model_executor/kernels/linear/
|
||||
- vllm/distributed/
|
||||
- csrc/moe/
|
||||
commands:
|
||||
- pytest -s -v evals/gsm8k/test_gsm8k_correctness.py --config-list-file=evals/gsm8k/configs/humming/config-deepep.txt
|
||||
|
||||
- label: ":nvidia: (B200) LM Eval Humming Default TEMPORARY"
|
||||
key: lm-eval-humming-b200-default
|
||||
optional: true
|
||||
timeout_in_minutes: 50
|
||||
device: b200-k8s
|
||||
optional: true
|
||||
num_devices: 1
|
||||
source_file_dependencies:
|
||||
- vllm/model_executor/layers/quantization/humming.py
|
||||
- vllm/model_executor/layers/quantization/utils/humming_utils.py
|
||||
- vllm/model_executor/layers/quantization/utils/humming/
|
||||
- vllm/model_executor/layers/fused_moe/experts/fused_humming_moe.py
|
||||
- vllm/model_executor/layers/fused_moe/oracle/
|
||||
- vllm/model_executor/kernels/linear/
|
||||
@@ -426,14 +466,14 @@ steps:
|
||||
- pytest -s -v evals/gsm8k/test_gsm8k_correctness.py --config-list-file=evals/gsm8k/configs/humming/config.txt
|
||||
|
||||
- label: ":nvidia: (B200) LM Eval Humming Activation FP8/INT8 TEMPORARY"
|
||||
key: lm-eval-humming-act-b200
|
||||
key: lm-eval-humming-b200-act
|
||||
optional: true
|
||||
timeout_in_minutes: 50
|
||||
device: b200-k8s
|
||||
optional: true
|
||||
num_devices: 1
|
||||
source_file_dependencies:
|
||||
- vllm/model_executor/layers/quantization/humming.py
|
||||
- vllm/model_executor/layers/quantization/utils/humming_utils.py
|
||||
- vllm/model_executor/layers/quantization/utils/humming/
|
||||
- vllm/model_executor/layers/fused_moe/experts/fused_humming_moe.py
|
||||
- vllm/model_executor/layers/fused_moe/oracle/
|
||||
- vllm/model_executor/kernels/linear/
|
||||
|
||||
@@ -236,7 +236,7 @@ __device__ void _moe_align_block_size_small_batch_expert(
|
||||
int32_t* __restrict__ expert_map, int32_t num_experts, int32_t block_size,
|
||||
size_t numel, int32_t max_num_tokens_padded, int32_t max_num_m_blocks,
|
||||
int32_t inactive_expert_id, int32_t model_offset, int32_t topk_num,
|
||||
int32_t* token_mask, bool has_expert_map) {
|
||||
int32_t* token_mask, bool has_expert_map, int32_t* scatter_idx = nullptr) {
|
||||
// Compute input buffer offsets. Typically these will all be 0, except when
|
||||
// using Multi LoRA.
|
||||
int sorted_token_ids_offset = max_num_tokens_padded * model_offset;
|
||||
@@ -318,9 +318,14 @@ __device__ void _moe_align_block_size_small_batch_expert(
|
||||
}
|
||||
|
||||
for (size_t i = tid; i < numel; i += stride) {
|
||||
if (int expert_id = get_local_expert_id(i, topk_ids, expert_map,
|
||||
num_experts, has_expert_map);
|
||||
expert_id != -1) {
|
||||
int expert_id = get_local_expert_id(i, topk_ids, expert_map, num_experts,
|
||||
has_expert_map);
|
||||
bool valid =
|
||||
expert_id != -1 && (token_mask == nullptr || token_mask[i / topk_num]);
|
||||
if (scatter_idx != nullptr) {
|
||||
scatter_idx[i] = valid ? static_cast<int32_t>(i) : -1;
|
||||
}
|
||||
if (expert_id != -1) {
|
||||
int32_t rank_post_pad =
|
||||
tokens_cnts[tid * num_experts + expert_id] + cumsum[expert_id];
|
||||
|
||||
@@ -338,14 +343,20 @@ __device__ void _count_and_sort_expert_tokens(
|
||||
int32_t* __restrict__ sorted_token_ids, int32_t* __restrict__ cumsum_buffer,
|
||||
int32_t* __restrict__ expert_map, size_t numel, int32_t num_experts,
|
||||
int32_t max_num_tokens_padded, int32_t* __restrict__ token_mask,
|
||||
int32_t model_offset, int32_t topk_num, bool has_expert_map) {
|
||||
int32_t model_offset, int32_t topk_num, bool has_expert_map,
|
||||
int32_t* scatter_idx = nullptr) {
|
||||
const size_t tid = blockIdx.y * blockDim.x + threadIdx.x;
|
||||
const size_t stride = blockDim.x * gridDim.y;
|
||||
|
||||
for (size_t i = tid; i < numel; i += stride) {
|
||||
if (int expert_id = get_local_expert_id(i, topk_ids, expert_map,
|
||||
num_experts, has_expert_map);
|
||||
expert_id != -1) {
|
||||
int expert_id = get_local_expert_id(i, topk_ids, expert_map, num_experts,
|
||||
has_expert_map);
|
||||
bool valid =
|
||||
expert_id != -1 && (token_mask == nullptr || token_mask[i / topk_num]);
|
||||
if (scatter_idx != nullptr) {
|
||||
scatter_idx[i] = valid ? static_cast<int32_t>(i) : -1;
|
||||
}
|
||||
if (expert_id != -1) {
|
||||
if (token_mask == nullptr || token_mask[i / topk_num]) {
|
||||
int32_t rank_post_pad = atomicAdd(
|
||||
&cumsum_buffer[(model_offset * (num_experts + 1)) + expert_id], 1);
|
||||
@@ -377,10 +388,11 @@ __global__ void count_and_sort_expert_tokens_kernel(
|
||||
const scalar_t* __restrict__ topk_ids,
|
||||
int32_t* __restrict__ sorted_token_ids, int32_t* __restrict__ cumsum_buffer,
|
||||
int32_t* __restrict__ expert_map, size_t numel, int32_t num_experts,
|
||||
int32_t max_num_tokens_padded, int32_t topk_num, bool has_expert_map) {
|
||||
int32_t max_num_tokens_padded, int32_t topk_num, bool has_expert_map,
|
||||
int32_t* scatter_idx) {
|
||||
_count_and_sort_expert_tokens(
|
||||
topk_ids, sorted_token_ids, cumsum_buffer, expert_map, numel, num_experts,
|
||||
max_num_tokens_padded, nullptr, 0, topk_num, has_expert_map);
|
||||
max_num_tokens_padded, nullptr, 0, topk_num, has_expert_map, scatter_idx);
|
||||
}
|
||||
|
||||
// Reduce the topk expert outputs per token (summed in fp32). The output is
|
||||
@@ -533,12 +545,12 @@ __global__ void moe_align_block_size_small_batch_expert_kernel(
|
||||
int32_t* __restrict__ total_tokens_post_pad,
|
||||
int32_t* __restrict__ expert_map, int32_t num_experts, int32_t block_size,
|
||||
size_t numel, int32_t max_num_tokens_padded, int32_t topk_num,
|
||||
bool has_expert_map) {
|
||||
bool has_expert_map, int32_t* scatter_idx) {
|
||||
_moe_align_block_size_small_batch_expert<scalar_t, fill_threads>(
|
||||
topk_ids, sorted_token_ids, expert_ids, total_tokens_post_pad, expert_map,
|
||||
num_experts, block_size, numel, max_num_tokens_padded,
|
||||
CEILDIV(max_num_tokens_padded, block_size), -1, 0, topk_num, nullptr,
|
||||
has_expert_map);
|
||||
has_expert_map, scatter_idx);
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
@@ -658,16 +670,31 @@ __global__ void moe_lora_align_block_size_small_batch_expert_kernel(
|
||||
|
||||
// taken from
|
||||
// https://github.com/sgl-project/sglang/blob/8b5f83ed3b7d2a49ad5c5cd5aa61c5d502f47dbc
|
||||
void moe_align_block_size(
|
||||
torch::stable::Tensor topk_ids, int64_t num_experts, int64_t block_size,
|
||||
torch::stable::Tensor sorted_token_ids, torch::stable::Tensor experts_ids,
|
||||
torch::stable::Tensor num_tokens_post_pad,
|
||||
std::optional<torch::stable::Tensor> maybe_expert_map) {
|
||||
void moe_align_block_size(torch::stable::Tensor topk_ids, int64_t num_experts,
|
||||
int64_t block_size,
|
||||
torch::stable::Tensor sorted_token_ids,
|
||||
torch::stable::Tensor experts_ids,
|
||||
torch::stable::Tensor num_tokens_post_pad,
|
||||
std::optional<torch::stable::Tensor> maybe_expert_map,
|
||||
std::optional<torch::stable::Tensor> scatter_idx) {
|
||||
const torch::stable::accelerator::DeviceGuard device_guard(
|
||||
topk_ids.get_device_index());
|
||||
const cudaStream_t stream =
|
||||
get_current_cuda_stream(topk_ids.get_device_index());
|
||||
|
||||
int32_t* scatter_ptr = nullptr;
|
||||
if (scatter_idx.has_value()) {
|
||||
STD_TORCH_CHECK(
|
||||
scatter_idx->scalar_type() == torch::headeronly::ScalarType::Int,
|
||||
"scatter_idx must be int32");
|
||||
STD_TORCH_CHECK(scatter_idx->device() == topk_ids.device() &&
|
||||
scatter_idx->is_contiguous() &&
|
||||
scatter_idx->numel() == topk_ids.numel(),
|
||||
"scatter_idx must be contiguous, on the same device, and "
|
||||
"match topk_ids.numel()");
|
||||
scatter_ptr = reinterpret_cast<int32_t*>(scatter_idx->mutable_data_ptr());
|
||||
}
|
||||
|
||||
int64_t padded_num_experts =
|
||||
((num_experts + WARP_SIZE - 1) / WARP_SIZE) * WARP_SIZE;
|
||||
int experts_per_warp = WARP_SIZE;
|
||||
@@ -713,7 +740,8 @@ void moe_align_block_size(
|
||||
num_tokens_post_pad.mutable_data_ptr()),
|
||||
reinterpret_cast<int32_t*>(expert_map.mutable_data_ptr()),
|
||||
num_experts, block_size, topk_ids.numel(),
|
||||
sorted_token_ids.size(0), topk_ids.size(1), has_expert_map);
|
||||
sorted_token_ids.size(0), topk_ids.size(1), has_expert_map,
|
||||
scatter_ptr);
|
||||
} else {
|
||||
torch::stable::Tensor cumsum_buffer = torch::stable::new_empty(
|
||||
topk_ids, {num_experts + 1}, torch::headeronly::ScalarType::Int);
|
||||
@@ -753,7 +781,7 @@ void moe_align_block_size(
|
||||
reinterpret_cast<int32_t*>(cumsum_buffer.mutable_data_ptr()),
|
||||
reinterpret_cast<int32_t*>(expert_map.mutable_data_ptr()),
|
||||
topk_ids.numel(), num_experts, sorted_token_ids.size(0),
|
||||
topk_ids.size(1), has_expert_map);
|
||||
topk_ids.size(1), has_expert_map, scatter_ptr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,11 +36,13 @@ void moe_sum(torch::stable::Tensor& input, torch::stable::Tensor& output,
|
||||
std::optional<torch::stable::Tensor> topk_ids,
|
||||
std::optional<torch::stable::Tensor> expert_map);
|
||||
|
||||
void moe_align_block_size(
|
||||
torch::stable::Tensor topk_ids, int64_t num_experts, int64_t block_size,
|
||||
torch::stable::Tensor sorted_token_ids, torch::stable::Tensor experts_ids,
|
||||
torch::stable::Tensor num_tokens_post_pad,
|
||||
std::optional<torch::stable::Tensor> maybe_expert_map);
|
||||
void moe_align_block_size(torch::stable::Tensor topk_ids, int64_t num_experts,
|
||||
int64_t block_size,
|
||||
torch::stable::Tensor sorted_token_ids,
|
||||
torch::stable::Tensor experts_ids,
|
||||
torch::stable::Tensor num_tokens_post_pad,
|
||||
std::optional<torch::stable::Tensor> maybe_expert_map,
|
||||
std::optional<torch::stable::Tensor> scatter_idx);
|
||||
|
||||
void batched_moe_align_block_size(
|
||||
int64_t max_tokens_per_batch, int64_t block_size,
|
||||
|
||||
@@ -56,20 +56,18 @@ int64_t moe_permute_sort_workspace_size(int64_t num_expanded_rows,
|
||||
CubKeyValueSorter::getWorkspaceSize(num_expanded_rows, n_expert));
|
||||
}
|
||||
|
||||
void moe_permute_impl(
|
||||
const torch::stable::Tensor& input, // [n_token, hidden]
|
||||
const torch::stable::Tensor& topk_ids, // [n_token, topk]
|
||||
const torch::stable::Tensor& token_expert_indices, // [n_token, topk]
|
||||
torch::stable::Tensor moe_sort_routing(
|
||||
const torch::stable::Tensor& topk_ids, // [n_token, topk]
|
||||
const torch::stable::Tensor& token_expert_indices, // [n_token, topk]
|
||||
const std::optional<torch::stable::Tensor>& expert_map, // [n_expert]
|
||||
int64_t n_expert, int64_t n_local_expert, int64_t topk,
|
||||
torch::stable::Tensor& permuted_input, // [permuted_size, hidden]
|
||||
torch::stable::Tensor& expert_first_token_offset, // [n_local_expert + 1]
|
||||
torch::stable::Tensor& inv_permuted_idx, // [n_token, topk]
|
||||
torch::stable::Tensor& permuted_idx, // [permute_size]
|
||||
const std::optional<torch::stable::Tensor>& maybe_sort_workspace,
|
||||
const std::optional<torch::stable::Tensor>& maybe_permuted_experts_id,
|
||||
const std::optional<torch::stable::Tensor>& maybe_sorted_row_idx,
|
||||
const std::optional<torch::stable::Tensor>& maybe_topk_ids_for_sort) {
|
||||
const std::optional<torch::stable::Tensor>& maybe_topk_ids_for_sort,
|
||||
bool build_inverse) {
|
||||
STD_TORCH_CHECK(expert_first_token_offset.scalar_type() ==
|
||||
torch::headeronly::ScalarType::Long,
|
||||
"expert_first_token_offset must be int64");
|
||||
@@ -88,12 +86,11 @@ void moe_permute_impl(
|
||||
"token_expert_indices shape must be same as inv_permuted_idx");
|
||||
|
||||
const torch::stable::accelerator::DeviceGuard device_guard(
|
||||
input.get_device_index());
|
||||
auto device = input.device();
|
||||
auto n_token = input.sizes()[0];
|
||||
auto n_hidden = input.sizes()[1];
|
||||
topk_ids.get_device_index());
|
||||
auto device = topk_ids.device();
|
||||
auto n_token = topk_ids.sizes()[0];
|
||||
auto expanded_rows = n_token * topk;
|
||||
auto stream = get_current_cuda_stream(input.get_device_index());
|
||||
auto stream = get_current_cuda_stream(topk_ids.get_device_index());
|
||||
|
||||
auto sorter_size = moe_permute_sort_workspace_size(expanded_rows, n_expert);
|
||||
auto sort_workspace = maybe_allocate_tensor(
|
||||
@@ -107,13 +104,11 @@ void moe_permute_impl(
|
||||
torch::headeronly::ScalarType::Int, device, "sorted_row_idx");
|
||||
|
||||
CubKeyValueSorter sorter{};
|
||||
int64_t* valid_num_ptr = nullptr;
|
||||
torch::stable::Tensor topk_ids_for_sort = topk_ids;
|
||||
|
||||
if (expert_map.has_value()) {
|
||||
const int* expert_map_ptr = get_ptr<int>(expert_map.value());
|
||||
valid_num_ptr =
|
||||
get_ptr<int64_t>(expert_first_token_offset) + n_local_expert;
|
||||
if (expert_map.has_value() || build_inverse) {
|
||||
const int* expert_map_ptr =
|
||||
expert_map.has_value() ? get_ptr<int>(expert_map.value()) : nullptr;
|
||||
topk_ids_for_sort = maybe_allocate_tensor(
|
||||
maybe_topk_ids_for_sort, topk_ids.sizes(),
|
||||
torch::headeronly::ScalarType::Int, device, "topk_ids_for_sort");
|
||||
@@ -126,7 +121,39 @@ void moe_permute_impl(
|
||||
get_ptr<const int>(topk_ids_for_sort), get_ptr<int>(token_expert_indices),
|
||||
get_ptr<int>(permuted_experts_id), get_ptr<int>(sorted_row_idx),
|
||||
get_ptr<int64_t>(expert_first_token_offset), n_token, n_expert,
|
||||
n_local_expert, topk, sorter, get_ptr<int>(sort_workspace), stream);
|
||||
n_local_expert, topk, sorter, get_ptr<int>(sort_workspace), stream,
|
||||
build_inverse ? get_ptr<int>(inv_permuted_idx) : nullptr);
|
||||
return sorted_row_idx;
|
||||
}
|
||||
|
||||
void moe_permute_impl(
|
||||
const torch::stable::Tensor& input, // [n_token, hidden]
|
||||
const torch::stable::Tensor& topk_ids, // [n_token, topk]
|
||||
const torch::stable::Tensor& token_expert_indices, // [n_token, topk]
|
||||
const std::optional<torch::stable::Tensor>& expert_map, // [n_expert]
|
||||
int64_t n_expert, int64_t n_local_expert, int64_t topk,
|
||||
torch::stable::Tensor& permuted_input, // [permuted_size, hidden]
|
||||
torch::stable::Tensor& expert_first_token_offset, // [n_local_expert + 1]
|
||||
torch::stable::Tensor& inv_permuted_idx, // [n_token, topk]
|
||||
torch::stable::Tensor& permuted_idx, // [permute_size]
|
||||
const std::optional<torch::stable::Tensor>& maybe_sort_workspace,
|
||||
const std::optional<torch::stable::Tensor>& maybe_permuted_experts_id,
|
||||
const std::optional<torch::stable::Tensor>& maybe_sorted_row_idx,
|
||||
const std::optional<torch::stable::Tensor>& maybe_topk_ids_for_sort) {
|
||||
const torch::stable::accelerator::DeviceGuard device_guard(
|
||||
input.get_device_index());
|
||||
auto sorted_row_idx = moe_sort_routing(
|
||||
topk_ids, token_expert_indices, expert_map, n_expert, n_local_expert,
|
||||
topk, expert_first_token_offset, inv_permuted_idx, maybe_sort_workspace,
|
||||
maybe_permuted_experts_id, maybe_sorted_row_idx, maybe_topk_ids_for_sort,
|
||||
false);
|
||||
auto stream = get_current_cuda_stream(input.get_device_index());
|
||||
auto n_token = input.size(0);
|
||||
auto n_hidden = input.size(1);
|
||||
auto valid_num_ptr =
|
||||
expert_map.has_value()
|
||||
? get_ptr<int64_t>(expert_first_token_offset) + n_local_expert
|
||||
: nullptr;
|
||||
|
||||
MOE_DISPATCH(input.scalar_type(), [&] {
|
||||
expandInputRowsKernelLauncher<scalar_t>(
|
||||
@@ -171,6 +198,22 @@ void moe_permute_with_scratch(
|
||||
topk_ids_for_sort);
|
||||
}
|
||||
|
||||
void moe_prepare_scatter(const torch::stable::Tensor& topk_ids,
|
||||
const torch::stable::Tensor& token_expert_indices,
|
||||
const std::optional<torch::stable::Tensor>& expert_map,
|
||||
int64_t n_expert, int64_t n_local_expert,
|
||||
torch::stable::Tensor& expert_first_token_offset,
|
||||
torch::stable::Tensor& scatter_idx,
|
||||
torch::stable::Tensor& sort_workspace,
|
||||
torch::stable::Tensor& sorted_experts,
|
||||
torch::stable::Tensor& sorted_rows,
|
||||
torch::stable::Tensor& topk_ids_for_sort) {
|
||||
moe_sort_routing(topk_ids, token_expert_indices, expert_map, n_expert,
|
||||
n_local_expert, topk_ids.size(1), expert_first_token_offset,
|
||||
scatter_idx, sort_workspace, sorted_experts, sorted_rows,
|
||||
topk_ids_for_sort, true);
|
||||
}
|
||||
|
||||
void moe_unpermute(
|
||||
const torch::stable::Tensor&
|
||||
permuted_hidden_states, // [n_token * topk, hidden]
|
||||
@@ -315,6 +358,19 @@ void moe_unpermute(
|
||||
STD_TORCH_CHECK(false, "moe_unpermute is not supported on CUDA < 12.0");
|
||||
}
|
||||
|
||||
void moe_prepare_scatter(const torch::stable::Tensor& topk_ids,
|
||||
const torch::stable::Tensor& token_expert_indices,
|
||||
const std::optional<torch::stable::Tensor>& expert_map,
|
||||
int64_t n_expert, int64_t n_local_expert,
|
||||
torch::stable::Tensor& expert_first_token_offset,
|
||||
torch::stable::Tensor& scatter_idx,
|
||||
torch::stable::Tensor& sort_workspace,
|
||||
torch::stable::Tensor& sorted_experts,
|
||||
torch::stable::Tensor& sorted_rows,
|
||||
torch::stable::Tensor& topk_ids_for_sort) {
|
||||
STD_TORCH_CHECK(false, "moe_prepare_scatter is not supported on CUDA < 12.0");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool moe_permute_unpermute_supported() {
|
||||
@@ -328,5 +384,6 @@ bool moe_permute_unpermute_supported() {
|
||||
STABLE_TORCH_LIBRARY_IMPL(_moe_C, CUDA, m) {
|
||||
m.impl("moe_permute", TORCH_BOX(&moe_permute));
|
||||
m.impl("moe_permute_with_scratch", TORCH_BOX(&moe_permute_with_scratch));
|
||||
m.impl("moe_prepare_scatter", TORCH_BOX(&moe_prepare_scatter));
|
||||
m.impl("moe_unpermute", TORCH_BOX(&moe_unpermute));
|
||||
}
|
||||
|
||||
+30
-24
@@ -82,34 +82,40 @@ __device__ inline int64_t findTotalEltsLessThanTarget(T const* sorted_indices,
|
||||
return target_location + 1;
|
||||
}
|
||||
|
||||
// Calculates the start offset of the tokens for a given expert. The last
|
||||
// element is the total number of valid tokens
|
||||
__global__ void computeExpertFirstTokenOffsetKernel(
|
||||
// Computes expert offsets (ending with the valid row count) and optional
|
||||
// inverse indices.
|
||||
__global__ void computeExpertOffsetsAndInverseKernel(
|
||||
int const* sorted_experts, int64_t const sorted_experts_len,
|
||||
int const num_experts, int64_t* expert_first_token_offset) {
|
||||
// First, compute the global tid. We only need 1 thread per expert.
|
||||
int const expert = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
int const num_experts, int64_t* expert_first_token_offset,
|
||||
int const* sorted_rows, int* inverse) {
|
||||
int const index = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
|
||||
// Note that expert goes [0, num_experts] (inclusive) because we want a count
|
||||
// for the total number of active tokens at the end of the scan.
|
||||
if (expert >= num_experts + 1) {
|
||||
return;
|
||||
if (index < num_experts + 1) {
|
||||
expert_first_token_offset[index] =
|
||||
findTotalEltsLessThanTarget(sorted_experts, sorted_experts_len, index);
|
||||
}
|
||||
if (inverse != nullptr && index < sorted_experts_len) {
|
||||
inverse[sorted_rows[index]] = index;
|
||||
}
|
||||
expert_first_token_offset[expert] =
|
||||
findTotalEltsLessThanTarget(sorted_experts, sorted_experts_len, expert);
|
||||
}
|
||||
|
||||
void computeExpertFirstTokenOffset(int const* sorted_indices,
|
||||
int const total_indices,
|
||||
int const num_experts,
|
||||
int64_t* expert_first_token_offset,
|
||||
cudaStream_t stream) {
|
||||
int const num_entries = num_experts + 1;
|
||||
int const threads = std::min(1024, num_entries);
|
||||
void computeExpertOffsetsAndInverse(int const* sorted_indices,
|
||||
int const total_indices,
|
||||
int const num_experts,
|
||||
int64_t* expert_first_token_offset,
|
||||
int const* sorted_rows, int* inverse,
|
||||
cudaStream_t stream) {
|
||||
int const num_entries = inverse != nullptr
|
||||
? std::max(num_experts + 1, total_indices)
|
||||
: num_experts + 1;
|
||||
int const threads = inverse != nullptr ? 256 : std::min(1024, num_entries);
|
||||
int const blocks = (num_entries + threads - 1) / threads;
|
||||
|
||||
computeExpertFirstTokenOffsetKernel<<<blocks, threads, 0, stream>>>(
|
||||
sorted_indices, total_indices, num_experts, expert_first_token_offset);
|
||||
computeExpertOffsetsAndInverseKernel<<<blocks, threads, 0, stream>>>(
|
||||
sorted_indices, total_indices, num_experts, expert_first_token_offset,
|
||||
sorted_rows, inverse);
|
||||
}
|
||||
|
||||
void sortAndScanExpert(const int* expert_for_source_row, const int* source_rows,
|
||||
@@ -117,7 +123,7 @@ void sortAndScanExpert(const int* expert_for_source_row, const int* source_rows,
|
||||
int64_t* expert_first_token_offset, int num_rows,
|
||||
int num_experts, int num_experts_per_node, int k,
|
||||
CubKeyValueSorter& sorter, void* sorter_ws,
|
||||
cudaStream_t stream) {
|
||||
cudaStream_t stream, int* inverse) {
|
||||
int64_t const expanded_num_rows = static_cast<int64_t>(k) * num_rows;
|
||||
// We need to use the full num_experts because that is the sentinel value used
|
||||
// by topk for disabled experts
|
||||
@@ -127,9 +133,9 @@ void sortAndScanExpert(const int* expert_for_source_row, const int* source_rows,
|
||||
sorter.run((void*)sorter_ws, sorter_ws_size_bytes, expert_for_source_row,
|
||||
permuted_experts, source_rows, permuted_rows, expanded_num_rows,
|
||||
stream);
|
||||
computeExpertFirstTokenOffset(permuted_experts, expanded_num_rows,
|
||||
num_experts_per_node, expert_first_token_offset,
|
||||
stream);
|
||||
computeExpertOffsetsAndInverse(
|
||||
permuted_experts, expanded_num_rows, num_experts_per_node,
|
||||
expert_first_token_offset, permuted_rows, inverse, stream);
|
||||
}
|
||||
|
||||
__global__ void preprocessTopkIdKernel(int* topk_id_ptr, int size,
|
||||
@@ -142,7 +148,7 @@ __global__ void preprocessTopkIdKernel(int* topk_id_ptr, int size,
|
||||
extern __shared__ int smem_expert_map[];
|
||||
// store expert_map in smem
|
||||
for (int i = tidx; i < num_experts; i += blockDim.x) {
|
||||
smem_expert_map[i] = expert_map_ptr[i];
|
||||
smem_expert_map[i] = expert_map_ptr ? expert_map_ptr[i] : i;
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
|
||||
@@ -43,18 +43,19 @@ class CubKeyValueSorter {
|
||||
int num_bits_;
|
||||
};
|
||||
|
||||
void computeExpertFirstTokenOffset(int const* sorted_indices,
|
||||
int const total_indices,
|
||||
int const num_experts,
|
||||
int64_t* expert_first_token_offset,
|
||||
cudaStream_t stream);
|
||||
void computeExpertOffsetsAndInverse(int const* sorted_indices,
|
||||
int const total_indices,
|
||||
int const num_experts,
|
||||
int64_t* expert_first_token_offset,
|
||||
int const* sorted_rows, int* inverse,
|
||||
cudaStream_t stream);
|
||||
|
||||
void sortAndScanExpert(const int* expert_for_source_row, const int* source_rows,
|
||||
int* permuted_experts, int* permuted_rows,
|
||||
int64_t* expert_first_token_offset, int num_rows,
|
||||
int num_experts, int num_experts_per_node, int k,
|
||||
CubKeyValueSorter& sorter, void* sorter_ws,
|
||||
cudaStream_t stream);
|
||||
cudaStream_t stream, int* inverse = nullptr);
|
||||
|
||||
template <typename T>
|
||||
void expandInputRowsKernelLauncher(
|
||||
|
||||
@@ -39,7 +39,8 @@ STABLE_TORCH_LIBRARY_FRAGMENT(_moe_C, m) {
|
||||
" int block_size, Tensor! sorted_token_ids,"
|
||||
" Tensor! experts_ids,"
|
||||
" Tensor! num_tokens_post_pad,"
|
||||
" Tensor? maybe_expert_map) -> ()");
|
||||
" Tensor? maybe_expert_map,"
|
||||
" Tensor(a!)? scatter_idx=None) -> ()");
|
||||
|
||||
// Aligning the number of tokens to be processed by each expert such
|
||||
// that it is divisible by the block size, but for the batched case.
|
||||
@@ -105,6 +106,13 @@ STABLE_TORCH_LIBRARY_FRAGMENT(_moe_C, m) {
|
||||
"permuted_idx, Tensor! sort_workspace, Tensor! permuted_experts_id, "
|
||||
"Tensor! sorted_row_idx, Tensor! topk_ids_for_sort)->()");
|
||||
|
||||
m.def(
|
||||
"moe_prepare_scatter(Tensor topk_ids, Tensor token_expert_indices, "
|
||||
"Tensor? expert_map, int n_expert, int n_local_expert, "
|
||||
"Tensor! expert_first_token_offset, Tensor! scatter_idx, "
|
||||
"Tensor! sort_workspace, Tensor! sorted_experts, Tensor! sorted_rows, "
|
||||
"Tensor! topk_ids_for_sort)->()");
|
||||
|
||||
m.def(
|
||||
"moe_unpermute(Tensor permuted_hidden_states, Tensor topk_weights,"
|
||||
"Tensor inv_permuted_idx, Tensor? expert_first_token_offset, "
|
||||
|
||||
@@ -33,4 +33,4 @@ quack-kernels==0.6.5 # Required by MSA
|
||||
tokenspeed-mla==0.1.8; platform_system == "Linux"
|
||||
|
||||
# Humming kernels for quantization gemm
|
||||
humming-kernels[cu13]==0.1.12
|
||||
humming-kernels[cu13]==0.1.15
|
||||
|
||||
@@ -9,3 +9,5 @@ server_args: >-
|
||||
--moe-backend humming
|
||||
--linear-backend humming
|
||||
--dtype bfloat16
|
||||
env:
|
||||
VLLM_HUMMING_MOE_GEMM_TYPE: "grouped_contiguous"
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
model_name: "nm-testing/Qwen3-30B-A3B-FP8-block"
|
||||
accuracy_threshold: 0.86
|
||||
num_questions: 1319
|
||||
num_fewshot: 5
|
||||
server_args: >-
|
||||
--enforce-eager
|
||||
--max-model-len 8192
|
||||
--moe-backend humming
|
||||
--linear-backend humming
|
||||
--data-parallel-size 2
|
||||
--enable-expert-parallel
|
||||
--all2all-backend deepep_high_throughput
|
||||
env:
|
||||
VLLM_HUMMING_MOE_GEMM_TYPE: "grouped_contiguous"
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
model_name: "nm-testing/Qwen3-30B-A3B-FP8-block"
|
||||
accuracy_threshold: 0.86
|
||||
num_questions: 1319
|
||||
num_fewshot: 5
|
||||
server_args: >-
|
||||
--enforce-eager
|
||||
--max-model-len 8192
|
||||
--moe-backend humming
|
||||
--linear-backend humming
|
||||
--data-parallel-size 2
|
||||
--enable-expert-parallel
|
||||
--all2all-backend deepep_high_throughput
|
||||
env:
|
||||
VLLM_HUMMING_MOE_GEMM_TYPE: "indexed"
|
||||
@@ -7,3 +7,5 @@ server_args: >-
|
||||
--max-model-len 8192
|
||||
--quantization humming
|
||||
--linear-backend humming
|
||||
env:
|
||||
VLLM_HUMMING_MOE_GEMM_TYPE: "grouped_contiguous"
|
||||
|
||||
@@ -7,3 +7,5 @@ server_args: >-
|
||||
--max-model-len 8192
|
||||
--moe-backend humming
|
||||
--linear-backend humming
|
||||
env:
|
||||
VLLM_HUMMING_MOE_GEMM_TYPE: "grouped_contiguous"
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
model_name: "Qwen/Qwen3-30B-A3B"
|
||||
accuracy_threshold: 0.80
|
||||
num_questions: 1319
|
||||
num_fewshot: 5
|
||||
server_args: >-
|
||||
--enforce-eager
|
||||
--max-model-len 8192
|
||||
--tensor-parallel-size 1
|
||||
--quantization humming
|
||||
--kernel-config.enable_flashinfer_autotune=False
|
||||
env:
|
||||
VLLM_HUMMING_ONLINE_QUANT_CONFIG: '{"dtype":"int5","hadamard_block_size":-1}'
|
||||
VLLM_HUMMING_INPUT_QUANT_CONFIG: '{"dtype":"float8e4m3"}'
|
||||
+4
-3
@@ -1,7 +1,8 @@
|
||||
model_name: "Qwen/Qwen3-30B-A3B"
|
||||
accuracy_threshold: 0.80
|
||||
model_name: "Qwen/Qwen3.5-35B-A3B"
|
||||
accuracy_threshold: 0.90
|
||||
num_questions: 1319
|
||||
num_fewshot: 5
|
||||
gen_prefix: " <think>\n\n</think>\n"
|
||||
server_args: >-
|
||||
--enforce-eager
|
||||
--max-model-len 8192
|
||||
@@ -9,5 +10,5 @@ server_args: >-
|
||||
--quantization humming
|
||||
--kernel-config.enable_flashinfer_autotune=False
|
||||
env:
|
||||
VLLM_HUMMING_ONLINE_QUANT_CONFIG: '{"dtype":"int5","hadamard_block_size":-1}'
|
||||
VLLM_HUMMING_ONLINE_QUANT_CONFIG: '{"dtype":"int6","hadamard_block_size":-1}'
|
||||
VLLM_HUMMING_INPUT_QUANT_CONFIG: '{"dtype":"int8"}'
|
||||
+4
-3
@@ -1,7 +1,8 @@
|
||||
model_name: "Qwen/Qwen3-30B-A3B"
|
||||
accuracy_threshold: 0.80
|
||||
model_name: "Qwen/Qwen3.5-35B-A3B"
|
||||
accuracy_threshold: 0.90
|
||||
num_questions: 1319
|
||||
num_fewshot: 5
|
||||
gen_prefix: " <think>\n\n</think>\n"
|
||||
server_args: >-
|
||||
--enforce-eager
|
||||
--max-model-len 8192
|
||||
@@ -9,4 +10,4 @@ server_args: >-
|
||||
--quantization humming
|
||||
--kernel-config.enable_flashinfer_autotune=False
|
||||
env:
|
||||
VLLM_HUMMING_ONLINE_QUANT_CONFIG: '{"dtype":"int5","hadamard_block_size":-1}'
|
||||
VLLM_HUMMING_ONLINE_QUANT_CONFIG: '{"dtype":"int6","hadamard_block_size":-1}'
|
||||
@@ -0,0 +1,13 @@
|
||||
model_name: "Qwen/Qwen3.5-9B"
|
||||
accuracy_threshold: 0.88
|
||||
num_questions: 1319
|
||||
num_fewshot: 5
|
||||
gen_prefix: " <think>\n\n</think>\n"
|
||||
server_args: >-
|
||||
--enforce-eager
|
||||
--max-model-len 8192
|
||||
--quantization humming
|
||||
--kernel-config.enable_flashinfer_autotune=False
|
||||
env:
|
||||
VLLM_HUMMING_ONLINE_QUANT_CONFIG: '{"dtype":"int6","hadamard_block_size":-1}'
|
||||
VLLM_HUMMING_INPUT_QUANT_CONFIG: '{"dtype":"int8"}'
|
||||
@@ -0,0 +1,12 @@
|
||||
model_name: "Qwen/Qwen3.5-9B"
|
||||
accuracy_threshold: 0.88
|
||||
num_questions: 1319
|
||||
num_fewshot: 5
|
||||
gen_prefix: " <think>\n\n</think>\n"
|
||||
server_args: >-
|
||||
--enforce-eager
|
||||
--max-model-len 8192
|
||||
--quantization humming
|
||||
--kernel-config.enable_flashinfer_autotune=False
|
||||
env:
|
||||
VLLM_HUMMING_ONLINE_QUANT_CONFIG: '{"dtype":"int6","hadamard_block_size":-1}'
|
||||
@@ -0,0 +1,3 @@
|
||||
gpt-oss-20b-humming.yaml
|
||||
Qwen3.5-35B-A3B-experts-int8-humming-act-int8.yaml
|
||||
Qwen3.5-35B-A3B-int6wc-hadamard-humming-act-int8.yaml
|
||||
@@ -0,0 +1,4 @@
|
||||
Qwen3-30B-A3B-Instruct-2507-quantized.w8a8-humming-act-int8.yaml
|
||||
Qwen3-30B-A3B-GPTQ-Int4-humming-act-int8.yaml
|
||||
Qwen3-30B-A3B-AWQ-humming.yaml
|
||||
Qwen3-4B-mixed-quant-RTN-humming.yaml
|
||||
@@ -1,4 +1,5 @@
|
||||
Qwen3.5-35B-A3B-experts-int8-humming.yaml
|
||||
Qwen3.5-35B-A3B-int6wc-hadamard-humming.yaml
|
||||
gpt-oss-20b-humming.yaml
|
||||
Qwen3-30B-A3B-GPTQ-Int4-humming.yaml
|
||||
Qwen3-30B-A3B-FP8-block-humming.yaml
|
||||
|
||||
@@ -3,4 +3,5 @@ Qwen3-30B-A3B-Instruct-2507-quantized.w8a8-humming-act-int8.yaml
|
||||
Qwen3-30B-A3B-GPTQ-Int4-humming-act-int8.yaml
|
||||
Qwen3-30B-A3B-AWQ-humming.yaml
|
||||
Qwen3.5-35B-A3B-experts-int8-humming-act-int8.yaml
|
||||
Qwen3.5-35B-A3B-int6wc-hadamard-humming-act-int8.yaml
|
||||
Qwen3-4B-mixed-quant-RTN-humming.yaml
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Qwen3-30B-A3B-FP8-block-humming-deepep-ht-grouped.yaml
|
||||
Qwen3-30B-A3B-FP8-block-humming-deepep-ht-indexed.yaml
|
||||
@@ -1,4 +1,5 @@
|
||||
Qwen3.5-35B-A3B-experts-int8-humming.yaml
|
||||
Qwen3.5-35B-A3B-int6wc-hadamard-humming.yaml
|
||||
Qwen3-30B-A3B-Instruct-2507-quantized.w8a8-humming.yaml
|
||||
Qwen3-30B-A3B-Fp8-v1-humming.yaml
|
||||
Qwen3-30B-A3B-NVFP4-humming.yaml
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
Qwen3-30B-A3B-int5wc-hadamard-humming.yaml
|
||||
Qwen3-30B-A3B-int5wc-hadamard-humming-act-fp8.yaml
|
||||
Qwen3-30B-A3B-int5wc-hadamard-humming-act-int8.yaml
|
||||
@@ -0,0 +1,9 @@
|
||||
Qwen2-1.5B-Instruct-FP8W8-humming.yaml
|
||||
Qwen2-1.5B-Instruct-FP8W8-humming-act-fp8.yaml
|
||||
Qwen3-0.6B-MXFP8-humming.yaml
|
||||
Qwen3-0.6B-MXFP8-humming-act-fp8.yaml
|
||||
Qwen3-30B-A3B-AWQ-humming.yaml
|
||||
Qwen3-30B-A3B-GPTQ-Int4-humming-act-fp8.yaml
|
||||
gpt-oss-20b-humming.yaml
|
||||
Qwen3.5-9B-int6wc-hadamard-humming.yaml
|
||||
Qwen3.5-9B-int6wc-hadamard-humming-act-int8.yaml
|
||||
@@ -352,49 +352,46 @@ def test_masked_situ_and_mul(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("activation", "activation_config"),
|
||||
[
|
||||
(MoEActivation.SILU, ApplyMoEActivationConfig()),
|
||||
(
|
||||
MoEActivation.SILU,
|
||||
ApplyMoEActivationConfig(clamp_limit=3.0),
|
||||
MOE_ACTIVATION_CASES = [
|
||||
pytest.param(MoEActivation.SILU, ApplyMoEActivationConfig(), id="silu"),
|
||||
pytest.param(
|
||||
MoEActivation.SILU, ApplyMoEActivationConfig(clamp_limit=3.0), id="silu_clamp"
|
||||
),
|
||||
pytest.param(MoEActivation.GELU, ApplyMoEActivationConfig(), id="gelu"),
|
||||
pytest.param(MoEActivation.GELU_TANH, ApplyMoEActivationConfig(), id="gelu_tanh"),
|
||||
pytest.param(
|
||||
MoEActivation.SITU,
|
||||
ApplyMoEActivationConfig(
|
||||
activation_situ_beta=1.5,
|
||||
activation_situ_linear_beta=2.0,
|
||||
),
|
||||
(MoEActivation.GELU, ApplyMoEActivationConfig()),
|
||||
(MoEActivation.GELU_TANH, ApplyMoEActivationConfig()),
|
||||
(
|
||||
MoEActivation.SITU,
|
||||
ApplyMoEActivationConfig(
|
||||
activation_situ_beta=1.5,
|
||||
activation_situ_linear_beta=2.0,
|
||||
),
|
||||
),
|
||||
(MoEActivation.SWIGLUOAI, ApplyMoEActivationConfig()),
|
||||
(
|
||||
MoEActivation.SWIGLUOAI_UNINTERLEAVE,
|
||||
ApplyMoEActivationConfig(clamp_limit=3.0, alpha=1.3, beta=0.5),
|
||||
),
|
||||
(MoEActivation.SWIGLUSTEP, ApplyMoEActivationConfig()),
|
||||
(MoEActivation.SILU_NO_MUL, ApplyMoEActivationConfig()),
|
||||
(MoEActivation.GELU_NO_MUL, ApplyMoEActivationConfig()),
|
||||
(MoEActivation.GELU_TANH_NO_MUL, ApplyMoEActivationConfig()),
|
||||
(MoEActivation.RELU2_NO_MUL, ApplyMoEActivationConfig()),
|
||||
],
|
||||
ids=[
|
||||
"silu",
|
||||
"silu_clamp",
|
||||
"gelu",
|
||||
"gelu_tanh",
|
||||
"situ",
|
||||
"swigluoai",
|
||||
"swigluoai_uninterleave",
|
||||
"swiglustep",
|
||||
"silu_no_mul",
|
||||
"gelu_no_mul",
|
||||
"gelu_tanh_no_mul",
|
||||
"relu2_no_mul",
|
||||
],
|
||||
)
|
||||
id="situ",
|
||||
),
|
||||
pytest.param(MoEActivation.SWIGLUOAI, ApplyMoEActivationConfig(), id="swigluoai"),
|
||||
pytest.param(
|
||||
MoEActivation.SWIGLUOAI_UNINTERLEAVE,
|
||||
ApplyMoEActivationConfig(clamp_limit=3.0, alpha=1.3, beta=0.5),
|
||||
id="swigluoai_uninterleave",
|
||||
),
|
||||
pytest.param(MoEActivation.SWIGLUSTEP, ApplyMoEActivationConfig(), id="swiglustep"),
|
||||
pytest.param(
|
||||
MoEActivation.SILU_NO_MUL, ApplyMoEActivationConfig(), id="silu_no_mul"
|
||||
),
|
||||
pytest.param(
|
||||
MoEActivation.GELU_NO_MUL, ApplyMoEActivationConfig(), id="gelu_no_mul"
|
||||
),
|
||||
pytest.param(
|
||||
MoEActivation.GELU_TANH_NO_MUL,
|
||||
ApplyMoEActivationConfig(),
|
||||
id="gelu_tanh_no_mul",
|
||||
),
|
||||
pytest.param(
|
||||
MoEActivation.RELU2_NO_MUL, ApplyMoEActivationConfig(), id="relu2_no_mul"
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("activation", "activation_config"), MOE_ACTIVATION_CASES)
|
||||
@torch.inference_mode()
|
||||
def test_masked_moe_activation_dispatch(
|
||||
default_vllm_config,
|
||||
@@ -497,3 +494,73 @@ def test_activation(
|
||||
|
||||
out = torch.empty_like(x)
|
||||
opcheck(fn, (out, x))
|
||||
|
||||
|
||||
HUMMING_ACTIVATION_CASES = MOE_ACTIVATION_CASES + [
|
||||
pytest.param(MoEActivation.RELU2, ApplyMoEActivationConfig(), id="relu2"),
|
||||
]
|
||||
HUMMING_ACTIVATION_CASES += [
|
||||
pytest.param(
|
||||
MoEActivation.SITU,
|
||||
ApplyMoEActivationConfig(
|
||||
activation_situ_beta=1.5,
|
||||
activation_situ_linear_beta=linear_beta,
|
||||
),
|
||||
id=f"situ-linear-beta-{linear_beta}",
|
||||
)
|
||||
for linear_beta in (None, 0.0, -1.0)
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(not current_platform.is_cuda(), reason="Humming requires CUDA")
|
||||
@pytest.mark.parametrize(("activation", "activation_config"), HUMMING_ACTIVATION_CASES)
|
||||
@pytest.mark.parametrize("dtype", DTYPES)
|
||||
@pytest.mark.parametrize(("num_tokens", "d"), [(1, 512), (7, 768), (83, 512)])
|
||||
@torch.inference_mode()
|
||||
def test_humming_activation_matches_framework(
|
||||
activation: MoEActivation,
|
||||
activation_config: ApplyMoEActivationConfig,
|
||||
dtype: torch.dtype,
|
||||
num_tokens: int,
|
||||
d: int,
|
||||
) -> None:
|
||||
"""Compare activation math/layouts without quantization or Hadamard error."""
|
||||
pytest.importorskip("humming")
|
||||
from humming.ops import process_input
|
||||
|
||||
from vllm.model_executor.layers.quantization.utils.humming.activation import (
|
||||
get_humming_activation,
|
||||
)
|
||||
|
||||
set_random_seed(0)
|
||||
width = d * 2 if activation.is_gated else d
|
||||
x = 4 * torch.randn(num_tokens, width, dtype=dtype, device=CUDA_DEVICES[0])
|
||||
# Exercise zero, saturation, and both sides of the clamp limits (3 and 7).
|
||||
edges = x.new_tensor([0, 0.001, 1, 2.99, 3, 3.01, 6.99, 7, 7.01, 8, 16])
|
||||
edges = torch.cat((-edges[1:].flip(0), edges))
|
||||
x[0] = edges[torch.arange(width, device=x.device) % edges.numel()]
|
||||
|
||||
actual, _, _ = process_input(
|
||||
x,
|
||||
quant_mode="none",
|
||||
hadamard_block_size=0,
|
||||
**get_humming_activation(activation, activation_config),
|
||||
)
|
||||
expected = torch.empty(num_tokens, d, dtype=dtype, device=x.device)
|
||||
if activation == MoEActivation.RELU2:
|
||||
# apply_moe_activation has only the non-gated ReLU2 variant.
|
||||
gate, up = x.float().chunk(2, dim=-1)
|
||||
activated_gate = torch.empty_like(gate)
|
||||
apply_moe_activation(MoEActivation.RELU2_NO_MUL, activated_gate, gate.clone())
|
||||
expected.copy_(activated_gate * up)
|
||||
else:
|
||||
# The framework's non-gated ReLU2 path modifies its input in place.
|
||||
apply_moe_activation(
|
||||
activation, expected, x.clone(), activation_config=activation_config
|
||||
)
|
||||
torch.testing.assert_close(
|
||||
actual,
|
||||
expected,
|
||||
atol=get_default_atol(expected),
|
||||
rtol=get_default_rtol(expected),
|
||||
)
|
||||
|
||||
@@ -1245,7 +1245,7 @@ def _make_humming_indexed_experts(activation: MoEActivation):
|
||||
from vllm.model_executor.layers.fused_moe.experts.fused_humming_moe import (
|
||||
HummingIndexedExperts,
|
||||
)
|
||||
from vllm.model_executor.layers.quantization.utils import humming_utils
|
||||
from vllm.model_executor.layers.quantization.utils import humming as humming_utils
|
||||
from vllm.utils import humming
|
||||
|
||||
top_k, num_experts = 6, 12
|
||||
@@ -1306,6 +1306,56 @@ def _make_humming_indexed_experts(activation: MoEActivation):
|
||||
return experts, layer
|
||||
|
||||
|
||||
@pytest.mark.parametrize("valid_shape_m", [8192, 8193])
|
||||
@pytest.mark.parametrize("w2_block_size", [64, 96, 128])
|
||||
def test_humming_indexed_routing_matches_each_projection_tile(
|
||||
valid_shape_m: int, w2_block_size: int
|
||||
):
|
||||
"""Independently tuned projections preserve routed rows and expert identity."""
|
||||
from types import SimpleNamespace
|
||||
|
||||
from vllm.model_executor.layers.fused_moe.experts.fused_humming_moe import (
|
||||
HummingIndexedExperts,
|
||||
)
|
||||
|
||||
topk_ids = torch.tensor([[0, 1], [1, 2], [2, 0]], device="cuda")
|
||||
expert_map = torch.tensor([1, -1, 0], dtype=torch.int32, device="cuda")
|
||||
experts = SimpleNamespace(
|
||||
global_num_experts=3,
|
||||
estimate_local_valid_shape_m=lambda _: valid_shape_m,
|
||||
w13_tuning_config=[
|
||||
[0, 8192, {"block_shape": [64, 128, 64]}],
|
||||
[8192, 16384, {"block_shape": [128, 128, 64]}],
|
||||
],
|
||||
w2_tuning_config=[
|
||||
[0, 8192, {"block_shape": [64, 128, 64]}],
|
||||
[8192, 16384, {"block_shape": [w2_block_size, 128, 64]}],
|
||||
],
|
||||
compute_config_str="compute",
|
||||
w13_tuning_config_str="w13",
|
||||
w2_tuning_config_str="w2",
|
||||
)
|
||||
first, second, _ = HummingIndexedExperts.prepare_humming_moe_kwargs(
|
||||
experts, topk_ids, expert_map, None
|
||||
)
|
||||
block_sizes = (64, 64) if valid_shape_m == 8192 else (128, w2_block_size)
|
||||
expected_rows = torch.where(expert_map[topk_ids.flatten()] >= 0)[0]
|
||||
for kwargs, block_size in zip((first, second), block_sizes):
|
||||
padded = kwargs["num_tokens_padded"].item()
|
||||
assert padded % block_size == 0
|
||||
rows = kwargs["sorted_ids"][:padded].long()
|
||||
valid = rows < topk_ids.numel()
|
||||
torch.testing.assert_close(rows[valid].sort().values, expected_rows)
|
||||
routed_experts = kwargs["expert_ids"][: padded // block_size]
|
||||
torch.testing.assert_close(
|
||||
routed_experts.repeat_interleave(block_size)[valid],
|
||||
expert_map[topk_ids.flatten()[rows[valid]]],
|
||||
)
|
||||
assert (first["top_k"], second["top_k"]) == (2, 1)
|
||||
for key in ("sorted_ids", "expert_ids", "num_tokens_padded"):
|
||||
assert (first[key] is second[key]) == (block_sizes[0] == block_sizes[1])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("activation", list(MoEActivation))
|
||||
def test_humming_activation_metadata_tracks_shared_apply(activation: MoEActivation):
|
||||
from vllm.model_executor.layers.fused_moe.experts.fused_humming_moe import (
|
||||
@@ -1464,54 +1514,46 @@ def test_humming_delegates_to_instance_activation():
|
||||
def test_humming_grouped_apply_forwards_valid_prefix(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
from types import MethodType, SimpleNamespace
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock
|
||||
|
||||
import vllm.model_executor.layers.fused_moe.experts.fused_humming_moe as humming
|
||||
from vllm.model_executor.layers.fused_moe.experts.fused_humming_moe import (
|
||||
HummingExpertsBase,
|
||||
HummingGroupedExperts,
|
||||
)
|
||||
|
||||
activation_func = Mock()
|
||||
buffers = {
|
||||
"gate_up_output": torch.empty(6, 4),
|
||||
"activation_output": torch.empty(6, 2),
|
||||
"quanted_down_input": torch.empty(6, 2),
|
||||
"down_output": torch.empty(6, 4),
|
||||
}
|
||||
experts = SimpleNamespace(
|
||||
activation=activation_func,
|
||||
num_experts=2,
|
||||
estimate_local_valid_shape_m=lambda _: 6,
|
||||
prepare_buffers=lambda *_: buffers,
|
||||
_get_permute_scratch=lambda _: None,
|
||||
quantize_input=lambda _, *, inputs, input_scale=None, quanted_input: (
|
||||
inputs,
|
||||
input_scale,
|
||||
_get_permute_scratch=lambda _, *, indices_only=False: object(),
|
||||
process_input=Mock(
|
||||
side_effect=lambda _, **kwargs: (
|
||||
kwargs["inputs"],
|
||||
kwargs.get("input_scale"),
|
||||
None,
|
||||
)
|
||||
),
|
||||
humming_forward=Mock(),
|
||||
compute_config_str="",
|
||||
w13_tuning_config_str="",
|
||||
w2_tuning_config_str="",
|
||||
)
|
||||
experts.apply_activation = MethodType(HummingExpertsBase.apply_activation, experts)
|
||||
|
||||
hidden_states = torch.empty(3, 4)
|
||||
output = torch.empty_like(hidden_states)
|
||||
topk_ids = torch.zeros((3, 2), dtype=torch.int64)
|
||||
topk_weights = torch.ones_like(topk_ids, dtype=torch.float32)
|
||||
expected_counts = torch.tensor([4], dtype=torch.int32)
|
||||
expert_offsets = torch.tensor([0, 2, 4], dtype=torch.int64)
|
||||
monkeypatch.setattr(
|
||||
humming,
|
||||
"moe_permute",
|
||||
lambda **_: (
|
||||
torch.empty(6, 4),
|
||||
None,
|
||||
expert_offsets,
|
||||
torch.arange(6),
|
||||
None,
|
||||
),
|
||||
"moe_prepare_scatter",
|
||||
lambda *_: (expert_offsets, torch.arange(6).view(3, 2)),
|
||||
)
|
||||
monkeypatch.setattr(humming, "moe_unpermute", lambda **_: None)
|
||||
|
||||
@@ -1534,12 +1576,11 @@ def test_humming_grouped_apply_forwards_valid_prefix(
|
||||
apply_router_weight_on_input=False,
|
||||
)
|
||||
|
||||
activation_func.assert_called_once()
|
||||
call_kwargs = activation_func.call_args.kwargs
|
||||
call_kwargs = experts.process_input.call_args.kwargs
|
||||
assert call_kwargs["activation"] == MoEActivation.SITU
|
||||
assert call_kwargs["input"].shape == (6, 4)
|
||||
assert call_kwargs["output"].shape == (6, 2)
|
||||
torch.testing.assert_close(call_kwargs["valid_token_counts"], expected_counts)
|
||||
assert call_kwargs["inputs"].shape == (6, 4)
|
||||
assert call_kwargs["quanted_input"].shape == (6, 2)
|
||||
torch.testing.assert_close(call_kwargs["num_valid_tokens"], expert_offsets[-1:])
|
||||
|
||||
|
||||
def test_batched_marlin_activation_uses_expert_token_counts(
|
||||
@@ -1653,7 +1694,8 @@ def test_humming_gated_non_gated_shape_contract(activation: MoEActivation):
|
||||
activation=moe_config.activation,
|
||||
)
|
||||
assert buffer_metas["gate_up_output"]["shape"][-1] == gate_up_size
|
||||
assert buffer_metas["activation_output"]["shape"][-1] == intermediate_size
|
||||
assert "activation_output" not in buffer_metas
|
||||
assert buffer_metas["quanted_down_input"]["shape"][-1] == intermediate_size
|
||||
assert experts.moe_problem_size(
|
||||
a1=torch.empty(1, hidden_size),
|
||||
w1=torch.empty(num_experts, 1),
|
||||
@@ -1661,6 +1703,13 @@ def test_humming_gated_non_gated_shape_contract(activation: MoEActivation):
|
||||
topk_ids=torch.empty(1, top_k, dtype=torch.long),
|
||||
) == (num_experts, 1, intermediate_size, hidden_size, top_k)
|
||||
|
||||
# Quantized batched input storage must cover every dispatcher's token slots.
|
||||
experts.is_batched = lambda: True
|
||||
experts.max_num_tokens = 17
|
||||
experts.num_dispatchers = 2
|
||||
batched_metas, _ = experts.get_buffer_metas(17, top_k, activation)
|
||||
assert batched_metas["quanted_gate_up_input"]["shape"][0] == num_experts * 17 * 2
|
||||
|
||||
|
||||
def test_humming_indexed_writes_supplied_output_buffer():
|
||||
from vllm.forward_context import set_forward_context
|
||||
|
||||
@@ -22,6 +22,46 @@ BLOCK_SIZES = [32, 128]
|
||||
set_random_seed(0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("num_experts,num_tokens", [(32, 8), (256, 8), (256, 1024)])
|
||||
@pytest.mark.parametrize("use_expert_map", [False, True])
|
||||
def test_moe_align_scatter_masks_invalid_routes(
|
||||
num_experts, num_tokens, use_expert_map
|
||||
):
|
||||
topk_ids = torch.arange(num_tokens * 4, device="cuda").reshape(-1, 4)
|
||||
topk_ids %= num_experts
|
||||
topk_ids[0] = torch.tensor([-1, num_experts, 0, 1], device="cuda")
|
||||
expert_map = None
|
||||
valid = (topk_ids >= 0) & (topk_ids < num_experts)
|
||||
if use_expert_map:
|
||||
expert_map = torch.full((num_experts,), -1, dtype=torch.int32, device="cuda")
|
||||
expert_map[::2] = torch.arange(
|
||||
num_experts // 2, device="cuda", dtype=torch.int32
|
||||
)
|
||||
valid &= topk_ids % 2 == 0
|
||||
|
||||
sorted_ids, expert_ids, num_padded, scatter_idx = moe_align_block_size(
|
||||
topk_ids,
|
||||
32,
|
||||
num_experts,
|
||||
expert_map,
|
||||
ignore_invalid_experts=True,
|
||||
return_scatter_idx=True,
|
||||
)
|
||||
expected = torch.arange(topk_ids.numel(), device="cuda", dtype=torch.int32)
|
||||
expected[~valid.flatten()] = -1
|
||||
torch.testing.assert_close(scatter_idx, expected[:, None])
|
||||
active_sorted = sorted_ids[: num_padded.item()]
|
||||
active_sorted = active_sorted[active_sorted < topk_ids.numel()]
|
||||
torch.testing.assert_close(active_sorted.sort().values, expected[expected >= 0])
|
||||
|
||||
baseline = moe_align_block_size(
|
||||
topk_ids, 32, num_experts, expert_map, ignore_invalid_experts=True
|
||||
)
|
||||
assert len(baseline) == 3
|
||||
torch.testing.assert_close(num_padded, baseline[2])
|
||||
torch.testing.assert_close(expert_ids, baseline[1])
|
||||
|
||||
|
||||
def _group_tokens_by_expert(
|
||||
sorted_ids: torch.Tensor,
|
||||
expert_ids: torch.Tensor,
|
||||
|
||||
@@ -18,6 +18,7 @@ from vllm.model_executor.layers.fused_moe.moe_permute_unpermute import (
|
||||
get_moe_permute_scratch,
|
||||
moe_permute,
|
||||
moe_permute_unpermute_supported,
|
||||
moe_prepare_scatter,
|
||||
moe_unpermute,
|
||||
)
|
||||
from vllm.platforms import current_platform
|
||||
@@ -458,7 +459,7 @@ def test_moe_permute_ignores_invalid_expert_ids_with_scratch() -> None:
|
||||
hidden_dtype=hidden_states.dtype,
|
||||
)
|
||||
|
||||
permuted, _, expert_offsets, _, _ = moe_permute(
|
||||
permuted, _, expert_offsets, inverse, _ = moe_permute(
|
||||
hidden_states=hidden_states,
|
||||
a1q_scale=None,
|
||||
topk_ids=topk_ids,
|
||||
@@ -473,3 +474,27 @@ def test_moe_permute_ignores_invalid_expert_ids_with_scratch() -> None:
|
||||
torch.tensor([0, 1, 2], dtype=torch.int64, device="cuda"),
|
||||
)
|
||||
torch.testing.assert_close(permuted[:2], hidden_states[[0, 2]])
|
||||
assert torch.all(inverse[[1, 3, 4]] >= expert_offsets[-1])
|
||||
expected = torch.zeros_like(hidden_states)
|
||||
expected[[0, 2]] = hidden_states[[0, 2]]
|
||||
output = torch.empty_like(hidden_states)
|
||||
moe_unpermute(
|
||||
output, permuted, torch.ones(5, 1, device="cuda"), inverse, expert_offsets
|
||||
)
|
||||
torch.testing.assert_close(output, expected)
|
||||
|
||||
expected_inverse = inverse.clone()
|
||||
expected_offsets = expert_offsets.clone()
|
||||
expert_offsets, indices = moe_prepare_scatter(topk_ids, expert_map, scratch)
|
||||
torch.testing.assert_close(indices.flatten(), expected_inverse)
|
||||
torch.testing.assert_close(expert_offsets, expected_offsets)
|
||||
graph = torch.cuda.CUDAGraph()
|
||||
with torch.cuda.graph(graph):
|
||||
expert_offsets, indices = moe_prepare_scatter(topk_ids, expert_map, scratch)
|
||||
topk_ids.zero_()
|
||||
graph.replay()
|
||||
|
||||
expected_inverse = torch.arange(5, dtype=torch.int32, device="cuda")
|
||||
expected_offsets = torch.tensor([0, 5, 5], dtype=torch.int64, device="cuda")
|
||||
torch.testing.assert_close(indices.flatten(), expected_inverse)
|
||||
torch.testing.assert_close(expert_offsets, expected_offsets)
|
||||
|
||||
@@ -14,7 +14,6 @@ from vllm.model_executor.kernels.linear import (
|
||||
AiterMxfp4LinearKernel,
|
||||
EmulationMxfp4LinearKernel,
|
||||
FlashInferMxFp4LinearKernel,
|
||||
HummingMxFp4LinearKernel,
|
||||
MarlinMxFp4LinearKernel,
|
||||
MxFp4LinearKernel,
|
||||
MxFp4LinearLayerConfig,
|
||||
@@ -44,7 +43,7 @@ _TRUE_W4A4_KERNELS = [
|
||||
|
||||
# Weight-only (A16) kernels: they never quantize activations. They still accept
|
||||
# MXFP4 activation keys as an intentional compatibility fallback.
|
||||
_WEIGHT_ONLY_KERNELS = [MarlinMxFp4LinearKernel, HummingMxFp4LinearKernel]
|
||||
_WEIGHT_ONLY_KERNELS = [MarlinMxFp4LinearKernel]
|
||||
|
||||
|
||||
def test_can_implement_is_abstract():
|
||||
|
||||
@@ -15,6 +15,7 @@ it in vLLM comes down to two things, both exercised here:
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
from vllm.model_executor.layers.fused_moe.experts.fused_humming_moe import (
|
||||
HummingExpertsBase,
|
||||
@@ -56,8 +57,8 @@ def test_mxfp4_weight_with_per_token_fp8_activation_still_supported():
|
||||
],
|
||||
)
|
||||
def test_humming_fp8_input_schema_to_quant_key(group_size, expected_key):
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
_humming_input_schema_to_quant_key,
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
input_schema_to_quant_key,
|
||||
)
|
||||
from vllm.utils.humming import HummingInputSchema
|
||||
from vllm.utils.humming import dtypes as humming_dtypes
|
||||
@@ -66,18 +67,21 @@ def test_humming_fp8_input_schema_to_quant_key(group_size, expected_key):
|
||||
a_dtype=humming_dtypes.float8e4m3,
|
||||
input_scale_group_size=group_size,
|
||||
)
|
||||
assert _humming_input_schema_to_quant_key(schema) == expected_key
|
||||
assert input_schema_to_quant_key(schema, param_dtype=torch.bfloat16) == expected_key
|
||||
|
||||
|
||||
@pytest.mark.skipif(not has_humming(), reason="humming is not installed")
|
||||
def test_humming_bf16_input_schema_is_unquantized():
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
_humming_input_schema_to_quant_key,
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
input_schema_to_quant_key,
|
||||
)
|
||||
from vllm.utils.humming import HummingInputSchema
|
||||
|
||||
# No a_dtype -> unquantized (bf16/fp16) inputs -> None.
|
||||
assert _humming_input_schema_to_quant_key(HummingInputSchema()) is None
|
||||
assert (
|
||||
input_schema_to_quant_key(HummingInputSchema(), param_dtype=torch.bfloat16)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def test_block_fp8_activation_quant_config_is_block_quantized():
|
||||
@@ -86,7 +90,7 @@ def test_block_fp8_activation_quant_config_is_block_quantized():
|
||||
DeepEP prepare/finalize step quantize activations to block FP8 *before* the
|
||||
all-to-all dispatch instead of deferring to Humming -- and what makes
|
||||
HummingExpertsBase.expects_unquantized_inputs return False."""
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
make_humming_moe_quant_config,
|
||||
)
|
||||
from vllm.model_executor.layers.quantization.utils.quant_utils import GroupShape
|
||||
@@ -108,7 +112,7 @@ def test_default_activation_quant_config_defers_to_humming():
|
||||
"""Without a block activation shape the config stays per-token (the deferred
|
||||
path): Humming quantizes internally, preserving pre-existing behavior for
|
||||
every non-block-FP8 scheme."""
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
make_humming_moe_quant_config,
|
||||
)
|
||||
from vllm.platforms import current_platform
|
||||
|
||||
@@ -229,7 +229,7 @@ def test_moe_wna16_humming_adapter_repacks_uint8_tensors():
|
||||
|
||||
|
||||
def test_moe_wna16_uses_humming_quant_config(monkeypatch):
|
||||
from vllm.model_executor.layers.quantization.utils import humming_utils
|
||||
from vllm.model_executor.layers.quantization.utils import humming as humming_utils
|
||||
|
||||
method = object.__new__(MoeWNA16Method)
|
||||
method.wna16_backend = WNA16MoEBackend.HUMMING
|
||||
|
||||
@@ -2272,6 +2272,7 @@ def moe_align_block_size(
|
||||
experts_ids: torch.Tensor,
|
||||
num_tokens_post_pad: torch.Tensor,
|
||||
expert_map: torch.Tensor | None = None,
|
||||
scatter_idx: torch.Tensor | None = None,
|
||||
) -> None:
|
||||
torch.ops._moe_C.moe_align_block_size(
|
||||
topk_ids,
|
||||
@@ -2281,6 +2282,7 @@ def moe_align_block_size(
|
||||
experts_ids,
|
||||
num_tokens_post_pad,
|
||||
expert_map,
|
||||
scatter_idx,
|
||||
)
|
||||
|
||||
|
||||
|
||||
+2
-1
@@ -1524,7 +1524,8 @@ environment_variables: dict[str, Callable[[], Any]] = {
|
||||
"VLLM_HUMMING_ONLINE_QUANT_CONFIG": lambda: maybe_convert_json_str_or_file(
|
||||
os.environ.get("VLLM_HUMMING_ONLINE_QUANT_CONFIG", None)
|
||||
),
|
||||
# The activation dtype config for humming kernel
|
||||
# The activation dtype config for humming kernel. Explicit schemas disable
|
||||
# fallback unless the config includes "allow_fallback": true.
|
||||
"VLLM_HUMMING_INPUT_QUANT_CONFIG": lambda: maybe_convert_json_str_or_file(
|
||||
os.environ.get("VLLM_HUMMING_INPUT_QUANT_CONFIG", None)
|
||||
),
|
||||
|
||||
@@ -101,6 +101,7 @@ from vllm.model_executor.kernels.linear.mxfp6 import (
|
||||
from vllm.model_executor.kernels.linear.mxfp6.emulation import (
|
||||
EmulationMxfp6LinearKernel,
|
||||
)
|
||||
from vllm.model_executor.kernels.linear.mxfp6.humming import HummingMxFp6LinearKernel
|
||||
from vllm.model_executor.kernels.linear.mxfp8 import (
|
||||
Mxfp8LinearKernel,
|
||||
Mxfp8LinearLayerConfig,
|
||||
@@ -295,6 +296,7 @@ _LINEAR_BACKEND_KERNEL_MAP: dict[str, set[type]] = {
|
||||
HummingLinearKernel,
|
||||
HummingMxfp8LinearKernel,
|
||||
HummingMxFp4LinearKernel,
|
||||
HummingMxFp6LinearKernel,
|
||||
HummingNvFp4LinearKernel,
|
||||
},
|
||||
"marlin": {
|
||||
@@ -574,6 +576,7 @@ _POSSIBLE_NVFP4_KERNELS: dict[PlatformEnum, list[type[NvFp4LinearKernel]]] = {
|
||||
|
||||
_POSSIBLE_MXFP6_KERNELS: dict[PlatformEnum, list[type[MxFp6LinearKernel]]] = {
|
||||
PlatformEnum.CUDA: [
|
||||
HummingMxFp6LinearKernel,
|
||||
EmulationMxfp6LinearKernel,
|
||||
],
|
||||
PlatformEnum.ROCM: [
|
||||
@@ -1294,6 +1297,7 @@ __all__ = [
|
||||
"MxFp6LinearLayerConfig",
|
||||
"init_mxfp6_linear_kernel",
|
||||
"EmulationMxfp6LinearKernel",
|
||||
"HummingMxFp6LinearKernel",
|
||||
"AiterMxfp4LinearKernel",
|
||||
"EmulationMxfp4LinearKernel",
|
||||
"FlashInferMxFp4LinearKernel",
|
||||
|
||||
@@ -24,7 +24,7 @@ class HummingLinearKernel(MPLinearKernel):
|
||||
return True, None
|
||||
|
||||
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
convert_linear_layer_to_humming_standard,
|
||||
get_humming_linear_compute_config,
|
||||
prepare_humming_linear_layer_config,
|
||||
@@ -57,13 +57,14 @@ class HummingLinearKernel(MPLinearKernel):
|
||||
x: torch.Tensor,
|
||||
bias: torch.Tensor | None = None,
|
||||
) -> torch.Tensor:
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
apply_humming_linear,
|
||||
)
|
||||
|
||||
return apply_humming_linear(
|
||||
layer,
|
||||
x,
|
||||
skip_bias_add=bias is None,
|
||||
layer_config=self.layer_config,
|
||||
compute_config=self.compute_config,
|
||||
locks=self.locks,
|
||||
|
||||
@@ -3,21 +3,18 @@
|
||||
|
||||
import torch
|
||||
|
||||
from vllm.logger import init_logger
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
apply_humming_linear,
|
||||
convert_linear_layer_to_humming_standard,
|
||||
get_humming_linear_compute_config,
|
||||
prepare_humming_linear_layer_config,
|
||||
quant_key_to_input_schema,
|
||||
)
|
||||
from vllm.model_executor.layers.quantization.utils.quant_utils import kMxfp4Dynamic
|
||||
from vllm.platforms import current_platform
|
||||
from vllm.utils.import_utils import has_humming
|
||||
|
||||
from .base import MxFp4LinearKernel, MxFp4LinearLayerConfig
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
|
||||
class HummingMxFp4LinearKernel(MxFp4LinearKernel):
|
||||
"""Humming GEMM Kernel for MXFP4."""
|
||||
@@ -39,14 +36,10 @@ class HummingMxFp4LinearKernel(MxFp4LinearKernel):
|
||||
|
||||
@classmethod
|
||||
def can_implement(cls, config: MxFp4LinearLayerConfig) -> tuple[bool, str | None]:
|
||||
if config.activation_quant_key not in (None, kMxfp4Dynamic):
|
||||
return False, "only supports MXFP4 dynamic or unquantized activations"
|
||||
if config.activation_quant_key is not None:
|
||||
logger.warning_once(
|
||||
"HummingMxFp4LinearKernel is a weight-only (A16) kernel; "
|
||||
"the requested activation quantization (%s) is ignored.",
|
||||
config.activation_quant_key,
|
||||
)
|
||||
try:
|
||||
quant_key_to_input_schema(config.activation_quant_key)
|
||||
except ValueError as error:
|
||||
return False, str(error)
|
||||
return True, None
|
||||
|
||||
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
||||
@@ -62,7 +55,10 @@ class HummingMxFp4LinearKernel(MxFp4LinearKernel):
|
||||
}
|
||||
|
||||
convert_linear_layer_to_humming_standard(layer=layer, name_map=name_map)
|
||||
self.layer_config = prepare_humming_linear_layer_config(layer, quant_config)
|
||||
input_schema = quant_key_to_input_schema(self.config.activation_quant_key)
|
||||
self.layer_config = prepare_humming_linear_layer_config(
|
||||
layer, quant_config, input_schema=input_schema
|
||||
)
|
||||
self.compute_config = get_humming_linear_compute_config()
|
||||
self.locks = torch.zeros(1024, dtype=torch.int32, device=layer.weight.device)
|
||||
|
||||
@@ -75,6 +71,7 @@ class HummingMxFp4LinearKernel(MxFp4LinearKernel):
|
||||
return apply_humming_linear(
|
||||
layer,
|
||||
x,
|
||||
skip_bias_add=bias is None,
|
||||
layer_config=self.layer_config,
|
||||
compute_config=self.compute_config,
|
||||
locks=self.locks,
|
||||
|
||||
@@ -13,8 +13,8 @@ from vllm.model_executor.layers.quantization.utils.quant_utils import QuantKey
|
||||
class MxFp6LinearLayerConfig:
|
||||
"""Configuration for an MXFP6 linear layer.
|
||||
|
||||
All MXFP6 layers share the same structure: packed uint8 weights (2 FP4 values per
|
||||
byte) and per-block weight scales (group size 32).
|
||||
All MXFP6 layers share the same structure: packed uint8 weights (4 FP6 values
|
||||
per 3 bytes) and per-block weight scales (group size 32).
|
||||
|
||||
Attributes:
|
||||
weight_quant_key: Identifies the weight quantization format. Can be
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
import torch
|
||||
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
apply_humming_linear,
|
||||
convert_linear_layer_to_humming_standard,
|
||||
get_humming_linear_compute_config,
|
||||
prepare_humming_linear_layer_config,
|
||||
quant_key_to_input_schema,
|
||||
)
|
||||
from vllm.model_executor.layers.quantization.utils.quant_utils import kMxfp6E2M3Static
|
||||
from vllm.platforms import current_platform
|
||||
from vllm.utils.import_utils import has_humming
|
||||
|
||||
from .base import MxFp6LinearKernel, MxFp6LinearLayerConfig
|
||||
|
||||
|
||||
class HummingMxFp6LinearKernel(MxFp6LinearKernel):
|
||||
"""Humming GEMM for packed MXFP6 E2M3 and E3M2 weights."""
|
||||
|
||||
@classmethod
|
||||
def is_supported(
|
||||
cls, compute_capability: int | None = None
|
||||
) -> tuple[bool, str | None]:
|
||||
if not current_platform.is_cuda():
|
||||
return False, "Humming only supported on CUDA"
|
||||
|
||||
if not has_humming():
|
||||
return False, "Humming is not installed"
|
||||
|
||||
if not current_platform.has_device_capability(75):
|
||||
return False, "Humming only supported on SM75+"
|
||||
|
||||
return True, None
|
||||
|
||||
@classmethod
|
||||
def can_implement(cls, config: MxFp6LinearLayerConfig) -> tuple[bool, str | None]:
|
||||
try:
|
||||
quant_key_to_input_schema(config.activation_quant_key)
|
||||
except ValueError as error:
|
||||
return False, str(error)
|
||||
return True, None
|
||||
|
||||
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
||||
weight_dtype = "float6e3m2"
|
||||
if self.config.weight_quant_key == kMxfp6E2M3Static:
|
||||
weight_dtype = "float6e2m3"
|
||||
|
||||
quant_config = {
|
||||
"quant_method": "humming",
|
||||
"dtype": weight_dtype,
|
||||
"scale_dtype": "float8e8m0",
|
||||
"group_size": 32,
|
||||
"weight_scale_type": "group",
|
||||
}
|
||||
layer.weight_scale.data = layer.weight_scale.data.view(torch.float8_e8m0fnu)
|
||||
|
||||
convert_linear_layer_to_humming_standard(
|
||||
layer=layer,
|
||||
name_map={"weight": "weight", "weight_scale": "weight_scale"},
|
||||
)
|
||||
input_schema = quant_key_to_input_schema(self.config.activation_quant_key)
|
||||
self.layer_config = prepare_humming_linear_layer_config(
|
||||
layer, quant_config, input_schema=input_schema
|
||||
)
|
||||
self.compute_config = get_humming_linear_compute_config()
|
||||
self.locks = torch.zeros(1024, dtype=torch.int32, device=layer.weight.device)
|
||||
|
||||
def apply_weights(
|
||||
self,
|
||||
layer: torch.nn.Module,
|
||||
x: torch.Tensor,
|
||||
bias: torch.Tensor | None = None,
|
||||
) -> torch.Tensor:
|
||||
return apply_humming_linear(
|
||||
layer,
|
||||
x,
|
||||
skip_bias_add=bias is None,
|
||||
layer_config=self.layer_config,
|
||||
compute_config=self.compute_config,
|
||||
locks=self.locks,
|
||||
)
|
||||
@@ -3,12 +3,14 @@
|
||||
|
||||
import torch
|
||||
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
apply_humming_linear,
|
||||
convert_linear_layer_to_humming_standard,
|
||||
get_humming_linear_compute_config,
|
||||
prepare_humming_linear_layer_config,
|
||||
quant_key_to_input_schema,
|
||||
)
|
||||
from vllm.model_executor.layers.quantization.utils.quant_utils import kMxfp8Dynamic
|
||||
from vllm.platforms import current_platform
|
||||
from vllm.utils.import_utils import has_humming
|
||||
|
||||
@@ -50,7 +52,10 @@ class HummingMxfp8LinearKernel(Mxfp8LinearKernel):
|
||||
}
|
||||
|
||||
convert_linear_layer_to_humming_standard(layer=layer, name_map=name_map)
|
||||
self.layer_config = prepare_humming_linear_layer_config(layer, quant_config)
|
||||
input_schema = quant_key_to_input_schema(kMxfp8Dynamic)
|
||||
self.layer_config = prepare_humming_linear_layer_config(
|
||||
layer, quant_config, input_schema=input_schema
|
||||
)
|
||||
self.compute_config = get_humming_linear_compute_config()
|
||||
self.locks = torch.zeros(1024, dtype=torch.int32, device=layer.weight.device)
|
||||
|
||||
@@ -63,6 +68,7 @@ class HummingMxfp8LinearKernel(Mxfp8LinearKernel):
|
||||
return apply_humming_linear(
|
||||
layer,
|
||||
x,
|
||||
skip_bias_add=bias is None,
|
||||
layer_config=self.layer_config,
|
||||
compute_config=self.compute_config,
|
||||
locks=self.locks,
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
import torch
|
||||
|
||||
from vllm.logger import init_logger
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
apply_humming_linear,
|
||||
get_humming_linear_compute_config,
|
||||
prepare_humming_linear_layer_config,
|
||||
quant_key_to_input_schema,
|
||||
)
|
||||
from vllm.model_executor.layers.quantization.utils.quant_utils import kNvfp4Dynamic
|
||||
from vllm.platforms import current_platform
|
||||
from vllm.utils.import_utils import has_humming
|
||||
|
||||
@@ -40,9 +42,7 @@ class HummingNvFp4LinearKernel(NvFp4LinearKernel):
|
||||
return True, None
|
||||
|
||||
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
||||
# Route through humming's compressed-tensors nvfp4 loader (same path as
|
||||
# the MoE oracle); the native group_tensor schema mishandles a scalar
|
||||
# global scale.
|
||||
# Reuse the CT loader for packed FP4 weights and tensor scales.
|
||||
quant_config = {
|
||||
"quant_method": "compressed-tensors",
|
||||
"format": "nvfp4-pack-quantized",
|
||||
@@ -60,7 +60,16 @@ class HummingNvFp4LinearKernel(NvFp4LinearKernel):
|
||||
layer.weight_global_scale = torch.nn.Parameter(
|
||||
1.0 / layer.weight_global_scale, requires_grad=False
|
||||
)
|
||||
self.layer_config = prepare_humming_linear_layer_config(layer, quant_config)
|
||||
input_quant_key = None
|
||||
if hasattr(layer, "input_global_scale"):
|
||||
layer.input_scale_2 = torch.nn.Parameter(
|
||||
layer.input_global_scale.reshape(-1), requires_grad=False
|
||||
)
|
||||
input_quant_key = kNvfp4Dynamic
|
||||
input_schema = quant_key_to_input_schema(input_quant_key)
|
||||
self.layer_config = prepare_humming_linear_layer_config(
|
||||
layer, quant_config, input_schema=input_schema
|
||||
)
|
||||
self.compute_config = get_humming_linear_compute_config()
|
||||
self.locks = torch.zeros(1024, dtype=torch.int32, device=layer.weight.device)
|
||||
|
||||
@@ -73,6 +82,7 @@ class HummingNvFp4LinearKernel(NvFp4LinearKernel):
|
||||
return apply_humming_linear(
|
||||
layer,
|
||||
x,
|
||||
skip_bias_add=bias is None,
|
||||
layer_config=self.layer_config,
|
||||
compute_config=self.compute_config,
|
||||
locks=self.locks,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import torch
|
||||
|
||||
from vllm.logger import init_logger
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
apply_humming_linear,
|
||||
convert_linear_layer_to_humming_standard,
|
||||
get_humming_linear_compute_config,
|
||||
@@ -92,6 +92,7 @@ class HummingFP8ScaledMMLinearKernel(FP8ScaledMMLinearKernel):
|
||||
return apply_humming_linear(
|
||||
layer,
|
||||
x,
|
||||
skip_bias_add=bias is None,
|
||||
layer_config=self.layer_config,
|
||||
compute_config=self.compute_config,
|
||||
locks=self.locks,
|
||||
@@ -158,6 +159,7 @@ class HummingInt8ScaledMMLinearKernel(Int8ScaledMMLinearKernel):
|
||||
return apply_humming_linear(
|
||||
layer,
|
||||
x,
|
||||
skip_bias_add=bias is None,
|
||||
layer_config=self.layer_config,
|
||||
compute_config=self.compute_config,
|
||||
locks=self.locks,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import json
|
||||
import math
|
||||
from dataclasses import replace
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
import torch
|
||||
@@ -30,6 +31,7 @@ from vllm.model_executor.layers.fused_moe.moe_permute_unpermute import (
|
||||
get_moe_permute_scratch,
|
||||
moe_permute,
|
||||
moe_permute_unpermute_supported,
|
||||
moe_prepare_scatter,
|
||||
moe_unpermute,
|
||||
)
|
||||
from vllm.model_executor.layers.fused_moe.topk_weight_and_reduce import (
|
||||
@@ -57,14 +59,16 @@ from vllm.model_executor.layers.quantization.utils.quant_utils import (
|
||||
kMxfp8Static,
|
||||
kNvfp4Dynamic,
|
||||
kNvfp4Static,
|
||||
kStaticTensorScale,
|
||||
)
|
||||
from vllm.platforms import current_platform
|
||||
from vllm.scalar_type import ScalarType
|
||||
from vllm.utils.import_utils import has_humming
|
||||
from vllm.utils.platform_utils import num_compute_units
|
||||
from vllm.v1.worker.workspace import current_workspace_manager
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
HummingMoEQuantConfig,
|
||||
)
|
||||
from vllm.utils.humming import (
|
||||
@@ -118,38 +122,9 @@ def get_humming_moe_gemm_type(moe_config: FusedMoEConfig | None = None) -> str:
|
||||
return gemm_type
|
||||
|
||||
|
||||
def _fixup_moe_tuning_config(tuning_config: list, max_k_block: int = 128) -> None:
|
||||
"""Fix up each MoE tile in place: cap the K-block and widen warp-N.
|
||||
|
||||
- block_shape[2] (K-block) > ``max_k_block``: the driver rejects the TMA
|
||||
descriptor at launch (CUDA_ERROR_MISALIGNED_ADDRESS). Cap at 128, which
|
||||
Humming already uses for larger M.
|
||||
- warp_shape[1] (warp-N) < 32: block-FP8 (group-128) activations route the
|
||||
w13 (gate/up) GEMM to a tuning table that pins warp-N to 16, under-filling
|
||||
the Hopper WGMMA N dimension and corrupting the GEMM output (gsm8k
|
||||
0.94 -> 0.89). Widen to 32 whenever block_n % 32 == 0 -- on every tile,
|
||||
not just the K-capped ones. w2 (down) already uses warp_n=32.
|
||||
"""
|
||||
logger.info_once("Attempting to override humming GEMM config")
|
||||
for entry in tuning_config:
|
||||
config = entry[2]
|
||||
block_shape = config.get("block_shape")
|
||||
if not (block_shape and len(block_shape) == 3):
|
||||
continue
|
||||
block_m, block_n, block_k = block_shape
|
||||
|
||||
logger.info_once(f"Overriding humming GEMM config. Previous config\n: {config}")
|
||||
if block_k > max_k_block:
|
||||
config["block_shape"] = [block_m, block_n, max_k_block]
|
||||
|
||||
warp_shape = config.get("warp_shape")
|
||||
if warp_shape and warp_shape[1] < 32 and block_n % 32 == 0:
|
||||
config["warp_shape"] = [warp_shape[0], 32, warp_shape[2]]
|
||||
|
||||
logger.info_once(f"Overridden humming GEMM config. Current config\n: {config}")
|
||||
|
||||
|
||||
class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
quant_config: "HummingMoEQuantConfig"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
moe_config: FusedMoEConfig,
|
||||
@@ -165,7 +140,7 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
self.locks = torch.zeros(1024, dtype=torch.int32, device=moe_config.device)
|
||||
self.num_experts = moe_config.num_local_experts
|
||||
self.global_num_experts = moe_config.num_experts
|
||||
self.quant_config = quant_config
|
||||
self.quant_config = humming_quant_config
|
||||
self.init_humming_moe()
|
||||
|
||||
if self.is_batched():
|
||||
@@ -199,31 +174,61 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
gemm_type=self.humming_gemm_type(),
|
||||
)
|
||||
self.compute_config_str = json.dumps(self.compute_config)
|
||||
# Fix up the heuristic-chosen tiles before freezing to JSON -- see
|
||||
# _fixup_moe_tuning_config.
|
||||
_fixup_moe_tuning_config(self.w13_tuning_config)
|
||||
_fixup_moe_tuning_config(self.w2_tuning_config)
|
||||
self.w13_tuning_config_str = json.dumps(self.w13_tuning_config)
|
||||
self.w2_tuning_config_str = json.dumps(self.w2_tuning_config)
|
||||
|
||||
def quantize_input(
|
||||
def process_input(
|
||||
self,
|
||||
sublayer_name: str,
|
||||
inputs: torch.Tensor,
|
||||
quanted_input: torch.Tensor | None,
|
||||
input_scale: torch.Tensor | None = None,
|
||||
) -> tuple[torch.Tensor, torch.Tensor | None]:
|
||||
from vllm.utils.humming import may_quant_input
|
||||
|
||||
# input_scale is set for block-FP8 (group-128) activations that were
|
||||
# quantized before the EP dispatch: may_quant_input then skips the
|
||||
# redundant w13 quantization and forwards the pre-computed scale.
|
||||
return may_quant_input(
|
||||
self.humming_configs[sublayer_name],
|
||||
inputs=inputs,
|
||||
input_scale=input_scale,
|
||||
quanted_input=quanted_input,
|
||||
expert_tokens: torch.Tensor | None = None,
|
||||
activation: MoEActivation | None = None,
|
||||
scatter_idx: torch.Tensor | None = None,
|
||||
num_valid_tokens: torch.Tensor | None = None,
|
||||
) -> tuple[torch.Tensor, torch.Tensor | None, torch.Tensor | None]:
|
||||
from vllm.model_executor.layers.quantization.utils.humming.activation import (
|
||||
get_humming_activation,
|
||||
)
|
||||
from vllm.utils.humming import may_process_input
|
||||
|
||||
quant_config = self.quant_config
|
||||
config = self.humming_configs[sublayer_name]
|
||||
if input_scale is not None:
|
||||
assert scatter_idx is None
|
||||
if activation is not None:
|
||||
raise ValueError("Cannot apply activation to prequantized input")
|
||||
return inputs, input_scale, None
|
||||
|
||||
activation_kwargs = {}
|
||||
if activation is not None:
|
||||
activation_config = self.activation_config
|
||||
activation_kwargs = get_humming_activation(activation, activation_config)
|
||||
|
||||
prefix = "w1" if sublayer_name == "w13" else "w2"
|
||||
scale = getattr(quant_config, f"{prefix}_input_scale")
|
||||
scale2 = getattr(quant_config, f"{prefix}_input_scale_2")
|
||||
mode = config.input_quant_mode
|
||||
layout = "grouped_mask" if self.is_batched() else "normal"
|
||||
if scatter_idx is not None:
|
||||
layout = "scatter"
|
||||
|
||||
inputs, group_scales, token_scales = may_process_input(
|
||||
config,
|
||||
inputs=inputs,
|
||||
outputs=quanted_input,
|
||||
token_scales=scale2 if mode.has_secondary_scale else scale,
|
||||
hadamard_block_size=getattr(quant_config, f"{prefix}_hadamard_block_size"),
|
||||
layout=layout,
|
||||
expert_tokens=expert_tokens if layout == "grouped_mask" else None,
|
||||
scatter_idx=scatter_idx,
|
||||
num_valid_tokens=num_valid_tokens,
|
||||
**activation_kwargs,
|
||||
)
|
||||
input_scale = group_scales if mode.has_group_scale else token_scales
|
||||
input_scale_2 = token_scales if mode.has_secondary_scale else None
|
||||
return inputs, input_scale, input_scale_2
|
||||
|
||||
def humming_forward(
|
||||
self,
|
||||
@@ -232,30 +237,30 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
weight: torch.Tensor,
|
||||
input_scale: torch.Tensor | None,
|
||||
outputs: torch.Tensor,
|
||||
input_scale_2: torch.Tensor | None = None,
|
||||
**kwargs: Any,
|
||||
) -> torch.Tensor:
|
||||
from vllm.utils.humming import humming_forward
|
||||
|
||||
is_w13 = sublayer_name == "w13"
|
||||
index = 1 if sublayer_name == "w13" else 2
|
||||
return humming_forward(
|
||||
self.humming_configs[sublayer_name],
|
||||
inputs=inputs,
|
||||
weight=weight,
|
||||
weight_scale=(
|
||||
self.quant_config.w1_scale if is_w13 else self.quant_config.w2_scale
|
||||
),
|
||||
zero_point=(self.quant_config.w1_zp if is_w13 else self.quant_config.w2_zp),
|
||||
bias=(self.quant_config.w1_bias if is_w13 else self.quant_config.w2_bias),
|
||||
weight_scale_2=(
|
||||
self.quant_config.g1_alphas if is_w13 else self.quant_config.g2_alphas
|
||||
),
|
||||
weight_scale=getattr(self.quant_config, f"w{index}_scale"),
|
||||
zero_point=getattr(self.quant_config, f"w{index}_zp"),
|
||||
bias=getattr(self.quant_config, f"w{index}_bias"),
|
||||
weight_scale_2=getattr(self.quant_config, f"g{index}_alphas"),
|
||||
input_scale=input_scale,
|
||||
input_scale_2=input_scale_2,
|
||||
outputs=outputs,
|
||||
locks=self.locks,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def _get_permute_scratch(self, topk: int) -> MoEPermuteScratch | None:
|
||||
def _get_permute_scratch(
|
||||
self, topk: int, indices_only: bool = False
|
||||
) -> MoEPermuteScratch | None:
|
||||
if not moe_permute_unpermute_supported():
|
||||
return None
|
||||
|
||||
@@ -270,8 +275,8 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
num_experts=self.moe_config.num_experts,
|
||||
num_local_experts=self.moe_config.num_local_experts,
|
||||
device=torch.device(self.moe_config.device),
|
||||
hidden_size=self.moe_config.hidden_dim,
|
||||
hidden_dtype=self.moe_config.in_dtype,
|
||||
hidden_size=None if indices_only else self.moe_config.hidden_dim,
|
||||
hidden_dtype=None if indices_only else self.moe_config.in_dtype,
|
||||
)
|
||||
|
||||
def get_global_valid_shape_m(self, topk_ids: torch.Tensor):
|
||||
@@ -302,6 +307,16 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
weight_key: QuantKey | None,
|
||||
activation_key: QuantKey | None,
|
||||
) -> bool:
|
||||
if (
|
||||
activation_key is not None
|
||||
and activation_key.scale == kNvfp4Dynamic.scale
|
||||
and activation_key.dtype == kNvfp4Dynamic.dtype
|
||||
and activation_key.scale2 is not None
|
||||
and not activation_key.scale2.static
|
||||
and activation_key.scale2.group_shape == GroupShape.PER_TOKEN
|
||||
and activation_key.scale2.dtype == torch.float32
|
||||
):
|
||||
activation_key = replace(activation_key, scale2=kStaticTensorScale)
|
||||
SUPPORTED_W_A = [
|
||||
(kMxfp4Static, None),
|
||||
(kMxfp4Static, kMxfp4Dynamic),
|
||||
@@ -343,7 +358,7 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
(kMxfp8Static, kMxfp8Dynamic),
|
||||
]
|
||||
return (weight_key, activation_key) in SUPPORTED_W_A or (
|
||||
activation_key in (None, kFp8DynamicTokenSym)
|
||||
activation_key in (None, kFp8DynamicTokenSym, kInt8DynamicTokenSym)
|
||||
and _is_supported_wna16_weight_key(weight_key)
|
||||
)
|
||||
|
||||
@@ -355,7 +370,7 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
to FP8 before dispatch sends FP8 rather than BF16 over the interconnect,
|
||||
and Humming then consumes the pre-quantized FP8 + scale directly (see
|
||||
the apply() methods, which forward the dispatch scale into
|
||||
HummingMethod.may_quant_input, and may_quant_input itself, which is a
|
||||
HummingExpertsBase.process_input, which is a
|
||||
no-op when an input scale is already supplied). The scale layout
|
||||
produced by vLLM's block-FP8 quantization ([M, K // 128] float32,
|
||||
row-major) matches what the Humming WGMMA grouped GEMM expects.
|
||||
@@ -373,7 +388,7 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
inputs).
|
||||
|
||||
Humming normally quantizes inputs internally via
|
||||
HummingMethod.may_quant_input() in apply(), so we defer quantization
|
||||
HummingExpertsBase.process_input() in apply(), so we defer quantization
|
||||
(return True) to avoid quantizing twice -- once in prepare and once in
|
||||
Humming's apply().
|
||||
|
||||
@@ -398,8 +413,6 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
|
||||
@staticmethod
|
||||
def _supports_activation(activation: MoEActivation) -> bool:
|
||||
# Humming uses apply_moe_activation() callback for activation,
|
||||
# so any activation supported there can be used here.
|
||||
return apply_moe_activation_supported(activation)
|
||||
|
||||
@staticmethod
|
||||
@@ -456,10 +469,9 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
assert intermediate_dim == self.adjust_N_for_activation(gate_up_dim, activation)
|
||||
|
||||
# hidden_states
|
||||
# (-> quanted_gate_up_input) (if not BF16/FP16 activation)
|
||||
# -> quanted_gate_up_input (Hadamard + optional quantization)
|
||||
# -> gate_up_output
|
||||
# -> activation_output
|
||||
# (-> quanted_down_input) (if not BF16/FP16 activation)
|
||||
# -> quanted_down_input (activation + Hadamard + optional quantization)
|
||||
# -> down_output
|
||||
# (-> output) (if not is_batched)
|
||||
# Neighboring nodes are required to utilize distinct workspaces.
|
||||
@@ -471,8 +483,8 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
max_num_tokens = self.max_num_tokens
|
||||
num_dispatchers = self.num_dispatchers
|
||||
assert max_num_tokens is not None and num_dispatchers is not None
|
||||
input_shape_m = num_experts * max_num_tokens
|
||||
real_shape_m = num_experts * max_num_tokens * num_dispatchers
|
||||
input_shape_m = real_shape_m
|
||||
output_shape = (num_experts, max_num_tokens * num_dispatchers, K)
|
||||
else:
|
||||
input_shape_m = M
|
||||
@@ -483,15 +495,18 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
|
||||
a_dtype = self.humming_configs["w13"].a_dtype
|
||||
c_dtype = self.humming_configs["w13"].c_dtype
|
||||
num_bits = a_dtype.num_bits
|
||||
down_a_dtype = w2_config.a_dtype
|
||||
torch_dtype_map = {
|
||||
dtypes.float16: torch.float16,
|
||||
dtypes.bfloat16: torch.bfloat16,
|
||||
dtypes.float32: torch.float32,
|
||||
dtypes.float8e3m4: torch.uint8,
|
||||
dtypes.float8e4m3: torch.float8_e4m3fn,
|
||||
dtypes.float8e5m2: torch.float8_e5m2,
|
||||
dtypes.int8: torch.int8,
|
||||
dtypes.int4: torch.uint8,
|
||||
dtypes.float4e0m3: torch.uint8,
|
||||
dtypes.float4e2m1: torch.uint8,
|
||||
}
|
||||
|
||||
buffer_metas = {
|
||||
@@ -503,13 +518,9 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
"shape": (real_shape_m, gate_up_dim),
|
||||
"dtype": torch_dtype_map[c_dtype],
|
||||
},
|
||||
"activation_output": {
|
||||
"shape": (real_shape_m, intermediate_dim),
|
||||
"dtype": torch_dtype_map[c_dtype],
|
||||
},
|
||||
"quanted_down_input": {
|
||||
"shape": (real_shape_m, intermediate_dim),
|
||||
"dtype": torch_dtype_map[a_dtype],
|
||||
"dtype": torch_dtype_map[down_a_dtype],
|
||||
},
|
||||
"down_output": {
|
||||
"shape": output_shape if self.is_batched() else (real_shape_m, K),
|
||||
@@ -523,7 +534,8 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
|
||||
for key in buffer_metas:
|
||||
meta = buffer_metas[key]
|
||||
if "quanted" in key and a_dtype.num_bits == 4:
|
||||
input_dtype = down_a_dtype if key == "quanted_down_input" else a_dtype
|
||||
if "quanted" in key and input_dtype.num_bits == 4:
|
||||
last_dim = meta["shape"][-1]
|
||||
if last_dim % 2 != 0:
|
||||
raise ValueError(
|
||||
@@ -532,25 +544,12 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
)
|
||||
meta["shape"] = meta["shape"][:-1] + (last_dim // 2,)
|
||||
|
||||
if num_bits == 16:
|
||||
required_buffers = ["gate_up_output", "activation_output", "down_output"]
|
||||
else:
|
||||
required_buffers = [
|
||||
"quanted_gate_up_input",
|
||||
"gate_up_output",
|
||||
"activation_output",
|
||||
"quanted_down_input",
|
||||
"down_output",
|
||||
]
|
||||
|
||||
# Note: The fused SITU+FP8 quant goes gate_up_output ->
|
||||
# quanted_down_input directly, never materializing
|
||||
# activation_output. Dropping activation_output from the chain
|
||||
# flips the even/odd workspace 2-coloring below so
|
||||
# gate_up_output and quanted_down_input land
|
||||
# on DIFFERENT workspaces.
|
||||
if self.fused_situ_quant_enabled(activation):
|
||||
required_buffers.remove("activation_output")
|
||||
required_buffers = [
|
||||
"quanted_gate_up_input",
|
||||
"gate_up_output",
|
||||
"quanted_down_input",
|
||||
"down_output",
|
||||
]
|
||||
|
||||
# batched moe use down_output as output
|
||||
if not self.is_batched():
|
||||
@@ -673,81 +672,6 @@ class HummingExpertsBase(mk.FusedMoEExpertsModular):
|
||||
activation_kwargs["valid_token_counts"] = valid_token_counts
|
||||
self.activation(**activation_kwargs)
|
||||
|
||||
def fused_situ_quant_enabled(self, activation: MoEActivation) -> bool:
|
||||
"""Whether the SITU activation + w2 quant can be fused into one kernel.
|
||||
|
||||
Fused only for k-major block-FP8 group-128 e4m3 with float32 scales --
|
||||
the sole layout situ_and_mul_quant supports. A float32 as_dtype rules out
|
||||
MXMMA (which uses e8m0 m-major scales), so a group-128 scale is
|
||||
guaranteed k-major. Everything else (m-major MXFP8, other group sizes,
|
||||
16-bit passthrough, non-SITU) falls back to the separate
|
||||
situ_and_mul + quantize_input path.
|
||||
"""
|
||||
if activation != MoEActivation.SITU:
|
||||
return False
|
||||
w2cfg = self.humming_configs["w2"]
|
||||
# Report the specific blocker once so a silent fallback to the unfused
|
||||
# situ_and_mul + quant pair is never a mystery in a trace.
|
||||
reason: str | None = None
|
||||
if not (w2cfg.a_dtype.num_bits == 8 and str(w2cfg.a_dtype) == "float8e4m3"):
|
||||
reason = f"w2 a_dtype is {w2cfg.a_dtype} (need float8e4m3)"
|
||||
elif w2cfg.input_scale_group_size != 128:
|
||||
reason = (
|
||||
f"w2 input_scale_group_size is {w2cfg.input_scale_group_size} "
|
||||
"(need 128)"
|
||||
)
|
||||
elif not (w2cfg.as_dtype is not None and str(w2cfg.as_dtype) == "float32"):
|
||||
reason = f"w2 as_dtype is {w2cfg.as_dtype} (need float32, k-major)"
|
||||
if reason is not None:
|
||||
logger.warning_once(
|
||||
"Humming fused SITU+FP8 quant disabled, using unfused "
|
||||
"situ_and_mul + quant: %s",
|
||||
reason,
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
def fused_situ_quant(
|
||||
self,
|
||||
gate_up_output: torch.Tensor,
|
||||
quanted_down_input: torch.Tensor,
|
||||
num_valid_tokens: torch.Tensor | None,
|
||||
topk: int = 1,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
"""Run the fused SITU activation + FP8 quant for the w2 input.
|
||||
|
||||
Returns ``(quanted_down_input, input_scale)`` in the same layout the
|
||||
unfused ``apply_activation`` + ``quantize_input("w2")`` pair produces: an
|
||||
fp8 [M, d] tensor plus a k-major block-FP8 float32 [M, d // 128] group
|
||||
scale. Only reached when ``fused_situ_quant_enabled`` (group_size 128).
|
||||
"""
|
||||
from vllm.model_executor.layers.fused_moe.activation import (
|
||||
situ_and_mul_quant,
|
||||
)
|
||||
|
||||
cfg = self.activation_config
|
||||
assert cfg.activation_situ_beta is not None, (
|
||||
"SITU requires activation_situ_beta from FusedMoEConfig"
|
||||
)
|
||||
group_size = self.humming_configs["w2"].input_scale_group_size
|
||||
m, d = quanted_down_input.size(0), quanted_down_input.size(1)
|
||||
input_scale = torch.empty(
|
||||
(m, d // group_size),
|
||||
dtype=torch.float32,
|
||||
device=quanted_down_input.device,
|
||||
)
|
||||
situ_and_mul_quant(
|
||||
quanted_down_input,
|
||||
input_scale,
|
||||
gate_up_output,
|
||||
beta=cfg.activation_situ_beta,
|
||||
linear_beta=cfg.activation_situ_linear_beta,
|
||||
group_size=group_size,
|
||||
num_valid_tokens=num_valid_tokens,
|
||||
topk=topk,
|
||||
)
|
||||
return quanted_down_input, input_scale
|
||||
|
||||
|
||||
class HummingIndexedExperts(HummingExpertsBase):
|
||||
def finalize_weight_and_reduce_impl(self) -> mk.TopKWeightAndReduce:
|
||||
@@ -768,7 +692,7 @@ class HummingIndexedExperts(HummingExpertsBase):
|
||||
topk_ids: torch.Tensor,
|
||||
expert_map: torch.Tensor | None,
|
||||
expert_tokens_meta: mk.ExpertTokensMetadata | None,
|
||||
) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
) -> tuple[dict[str, Any], dict[str, Any], torch.Tensor | None]:
|
||||
valid_shape_m = self.estimate_local_valid_shape_m(topk_ids)
|
||||
|
||||
moe_block_size = None
|
||||
@@ -784,13 +708,21 @@ class HummingIndexedExperts(HummingExpertsBase):
|
||||
)
|
||||
moe_block_size = 64
|
||||
|
||||
sorted_ids, expert_ids, num_tokens_padded = moe_align_block_size(
|
||||
use_scatter = False
|
||||
if expert_map is not None:
|
||||
num_sms = num_compute_units(topk_ids.get_device())
|
||||
use_scatter = topk_ids.numel() > 2 * num_sms
|
||||
|
||||
alignment = moe_align_block_size(
|
||||
topk_ids=topk_ids,
|
||||
block_size=moe_block_size,
|
||||
num_experts=self.global_num_experts,
|
||||
expert_map=expert_map,
|
||||
ignore_invalid_experts=True,
|
||||
return_scatter_idx=use_scatter,
|
||||
)
|
||||
sorted_ids, expert_ids, num_tokens_padded = alignment[:3]
|
||||
scatter_idx = alignment[3] if len(alignment) == 4 else None
|
||||
|
||||
moe_common_kwargs = {
|
||||
"sorted_ids": sorted_ids,
|
||||
@@ -806,7 +738,26 @@ class HummingIndexedExperts(HummingExpertsBase):
|
||||
moe_kwargs1.update(moe_common_kwargs)
|
||||
moe_kwargs2.update(moe_common_kwargs)
|
||||
|
||||
return moe_kwargs1, moe_kwargs2
|
||||
w2_block_size = 64
|
||||
for lower, upper, config in self.w2_tuning_config:
|
||||
if lower < valid_shape_m <= upper:
|
||||
w2_block_size = config["block_shape"][0]
|
||||
break
|
||||
if w2_block_size != moe_block_size:
|
||||
sorted_ids2, expert_ids2, num_tokens_padded2 = moe_align_block_size(
|
||||
topk_ids=topk_ids,
|
||||
block_size=w2_block_size,
|
||||
num_experts=self.global_num_experts,
|
||||
expert_map=expert_map,
|
||||
ignore_invalid_experts=True,
|
||||
)
|
||||
moe_kwargs2.update(
|
||||
sorted_ids=sorted_ids2,
|
||||
expert_ids=expert_ids2,
|
||||
num_tokens_padded=num_tokens_padded2,
|
||||
)
|
||||
|
||||
return moe_kwargs1, moe_kwargs2, scatter_idx
|
||||
|
||||
def apply(
|
||||
self,
|
||||
@@ -832,7 +783,7 @@ class HummingIndexedExperts(HummingExpertsBase):
|
||||
object, so w1, w2, a2_scale are unused. a1q_scale is None on the usual
|
||||
path (Humming quantizes the w13 input itself); for block-FP8 activations
|
||||
it carries the scale computed before dispatch, which is forwarded to
|
||||
may_quant_input so Humming skips the redundant w13 quantization. The
|
||||
process_input so Humming skips the redundant w13 quantization. The
|
||||
output is written into workspace13 via the buffer management.
|
||||
"""
|
||||
assert not apply_router_weight_on_input
|
||||
@@ -847,13 +798,13 @@ class HummingIndexedExperts(HummingExpertsBase):
|
||||
)
|
||||
buffers["output"] = output
|
||||
|
||||
moe_kwargs1, moe_kwargs2 = self.prepare_humming_moe_kwargs(
|
||||
moe_kwargs1, moe_kwargs2, scatter_idx = self.prepare_humming_moe_kwargs(
|
||||
topk_ids=topk_ids,
|
||||
expert_map=expert_map,
|
||||
expert_tokens_meta=expert_tokens_meta,
|
||||
)
|
||||
|
||||
inputs, input_scale = self.quantize_input(
|
||||
inputs, input_scale, input_scale_2 = self.process_input(
|
||||
"w13",
|
||||
inputs=hidden_states,
|
||||
input_scale=a1q_scale,
|
||||
@@ -865,52 +816,25 @@ class HummingIndexedExperts(HummingExpertsBase):
|
||||
inputs=inputs,
|
||||
weight=w1,
|
||||
input_scale=input_scale,
|
||||
input_scale_2=input_scale_2,
|
||||
outputs=buffers["gate_up_output"],
|
||||
**moe_kwargs1,
|
||||
)
|
||||
|
||||
# psum[-1:] is the DeepEP valid *token* count as a zero-cost int32 view.
|
||||
# The fused path and mul_sum consume tokens directly. The unfused
|
||||
# activation consumes the expanded row count (tokens * topk).
|
||||
valid_tokens = None
|
||||
topk = topk_ids.size(1)
|
||||
if (
|
||||
expert_tokens_meta is not None
|
||||
and expert_tokens_meta.psum_recv_per_rank is not None
|
||||
):
|
||||
valid_tokens = expert_tokens_meta.psum_recv_per_rank[-1:]
|
||||
|
||||
if self.fused_situ_quant_enabled(activation):
|
||||
# Fused SITU + FP8 quant (per-token or block-FP8 group-128) straight
|
||||
# into the w2 input, skipping the bf16 activation_output round-trip.
|
||||
inputs, input_scale = self.fused_situ_quant(
|
||||
gate_up_output=buffers["gate_up_output"],
|
||||
quanted_down_input=buffers["quanted_down_input"],
|
||||
num_valid_tokens=valid_tokens,
|
||||
topk=topk,
|
||||
)
|
||||
else:
|
||||
valid_token_counts = (
|
||||
valid_tokens * topk if valid_tokens is not None else None
|
||||
)
|
||||
self.apply_activation(
|
||||
activation=activation,
|
||||
input=buffers["gate_up_output"],
|
||||
output=buffers["activation_output"],
|
||||
valid_token_counts=valid_token_counts,
|
||||
)
|
||||
|
||||
inputs, input_scale = self.quantize_input(
|
||||
"w2",
|
||||
inputs=buffers["activation_output"],
|
||||
quanted_input=buffers.get("quanted_down_input", None),
|
||||
)
|
||||
inputs, input_scale, input_scale_2 = self.process_input(
|
||||
"w2",
|
||||
inputs=buffers["gate_up_output"],
|
||||
quanted_input=buffers["quanted_down_input"],
|
||||
activation=activation,
|
||||
scatter_idx=scatter_idx,
|
||||
)
|
||||
|
||||
self.humming_forward(
|
||||
"w2",
|
||||
inputs=inputs,
|
||||
weight=w2,
|
||||
input_scale=input_scale,
|
||||
input_scale_2=input_scale_2,
|
||||
outputs=buffers["down_output"].view(-1, hidden_states.size(-1)),
|
||||
**moe_kwargs2,
|
||||
)
|
||||
@@ -918,6 +842,10 @@ class HummingIndexedExperts(HummingExpertsBase):
|
||||
# expert_map masks any non-local id; num_valid_tokens bounds the
|
||||
# persistent kernel to the real token rows [0, num_recv) so the padding
|
||||
# tail is never iterated (CUDA-graph-safe device scalar).
|
||||
valid_tokens = None
|
||||
if expert_tokens_meta and expert_tokens_meta.psum_recv_per_rank is not None:
|
||||
valid_tokens = expert_tokens_meta.psum_recv_per_rank[-1:]
|
||||
|
||||
moe_fused_mul_sum(
|
||||
inputs=buffers["down_output"].view(*topk_ids.shape, -1),
|
||||
topk_weights=topk_weights,
|
||||
@@ -983,21 +911,33 @@ class HummingGroupedExperts(HummingExpertsBase):
|
||||
)
|
||||
buffers["output"] = output
|
||||
|
||||
hidden_states, a1q_scale, expert_first_token_offset, inv_perm, _ = moe_permute(
|
||||
hidden_states=hidden_states,
|
||||
a1q_scale=a1q_scale,
|
||||
topk_ids=topk_ids,
|
||||
n_expert=global_num_experts,
|
||||
n_local_expert=self.num_experts,
|
||||
expert_map=expert_map,
|
||||
scratch=self._get_permute_scratch(topk_ids.size(1)),
|
||||
)
|
||||
if a1q_scale is None:
|
||||
scratch = self._get_permute_scratch(topk_ids.size(1), indices_only=True)
|
||||
assert scratch is not None
|
||||
scatter_metadata = moe_prepare_scatter(topk_ids, expert_map, scratch)
|
||||
expert_offsets, scatter_idx = scatter_metadata
|
||||
inv_perm = scatter_idx.flatten()
|
||||
num_valid_tokens = expert_offsets[-1:] if expert_map is not None else None
|
||||
else:
|
||||
hidden_states, a1q_scale, expert_offsets, inv_perm, _ = moe_permute(
|
||||
hidden_states=hidden_states,
|
||||
a1q_scale=a1q_scale,
|
||||
topk_ids=topk_ids,
|
||||
n_expert=global_num_experts,
|
||||
n_local_expert=self.num_experts,
|
||||
expert_map=expert_map,
|
||||
scratch=self._get_permute_scratch(topk_ids.size(1)),
|
||||
)
|
||||
scatter_idx = None
|
||||
num_valid_tokens = None
|
||||
|
||||
inputs, input_scale = self.quantize_input(
|
||||
inputs, input_scale, input_scale_2 = self.process_input(
|
||||
"w13",
|
||||
inputs=hidden_states,
|
||||
input_scale=a1q_scale,
|
||||
quanted_input=buffers.get("quanted_gate_up_input", None),
|
||||
scatter_idx=scatter_idx,
|
||||
num_valid_tokens=num_valid_tokens,
|
||||
)
|
||||
|
||||
self.humming_forward(
|
||||
@@ -1005,28 +945,20 @@ class HummingGroupedExperts(HummingExpertsBase):
|
||||
inputs=inputs,
|
||||
weight=w1,
|
||||
input_scale=input_scale,
|
||||
input_scale_2=input_scale_2,
|
||||
outputs=buffers["gate_up_output"],
|
||||
valid_shape_m=valid_shape_m,
|
||||
expert_layout=expert_first_token_offset,
|
||||
expert_layout=expert_offsets,
|
||||
compute_config=self.compute_config_str,
|
||||
tuning_config=self.w13_tuning_config_str,
|
||||
)
|
||||
|
||||
self.apply_activation(
|
||||
activation=activation,
|
||||
input=buffers["gate_up_output"],
|
||||
output=buffers["activation_output"],
|
||||
valid_token_counts=(
|
||||
expert_first_token_offset[-1:].to(torch.int32)
|
||||
if expert_tokens_meta is None
|
||||
else None
|
||||
),
|
||||
)
|
||||
|
||||
inputs, input_scale = self.quantize_input(
|
||||
inputs, input_scale, input_scale_2 = self.process_input(
|
||||
"w2",
|
||||
inputs=buffers["activation_output"],
|
||||
quanted_input=buffers.get("quanted_down_input", None),
|
||||
inputs=buffers["gate_up_output"],
|
||||
quanted_input=buffers["quanted_down_input"],
|
||||
activation=activation,
|
||||
num_valid_tokens=expert_offsets[-1:],
|
||||
)
|
||||
|
||||
self.humming_forward(
|
||||
@@ -1034,9 +966,10 @@ class HummingGroupedExperts(HummingExpertsBase):
|
||||
inputs=inputs,
|
||||
weight=w2,
|
||||
input_scale=input_scale,
|
||||
input_scale_2=input_scale_2,
|
||||
outputs=buffers["down_output"],
|
||||
valid_shape_m=valid_shape_m,
|
||||
expert_layout=expert_first_token_offset,
|
||||
expert_layout=expert_offsets,
|
||||
compute_config=self.compute_config_str,
|
||||
tuning_config=self.w2_tuning_config_str,
|
||||
)
|
||||
@@ -1046,7 +979,7 @@ class HummingGroupedExperts(HummingExpertsBase):
|
||||
permuted_hidden_states=buffers["down_output"].view(*topk_ids.shape, -1),
|
||||
topk_weights=topk_weights,
|
||||
inv_permuted_idx=inv_perm,
|
||||
expert_first_token_offset=expert_first_token_offset,
|
||||
expert_first_token_offset=expert_offsets,
|
||||
)
|
||||
|
||||
|
||||
@@ -1088,7 +1021,7 @@ class BatchedHummingGroupedExperts(HummingExpertsBase):
|
||||
object, so w1, w2, a2_scale are unused. a1q_scale is None on the usual
|
||||
path (Humming quantizes the w13 input itself); for block-FP8 activations
|
||||
it carries the scale computed before dispatch, which is forwarded to
|
||||
may_quant_input so Humming skips the redundant w13 quantization. The
|
||||
process_input so Humming skips the redundant w13 quantization. The
|
||||
output is written into workspace13 via the buffer management.
|
||||
"""
|
||||
assert not apply_router_weight_on_input
|
||||
@@ -1111,11 +1044,12 @@ class BatchedHummingGroupedExperts(HummingExpertsBase):
|
||||
)
|
||||
buffers["down_output"] = output
|
||||
|
||||
inputs, input_scale = self.quantize_input(
|
||||
inputs, input_scale, input_scale_2 = self.process_input(
|
||||
"w13",
|
||||
inputs=hidden_states,
|
||||
input_scale=a1q_scale,
|
||||
quanted_input=buffers.get("quanted_gate_up_input", None),
|
||||
expert_tokens=expert_num_tokens,
|
||||
)
|
||||
|
||||
self.humming_forward(
|
||||
@@ -1123,6 +1057,7 @@ class BatchedHummingGroupedExperts(HummingExpertsBase):
|
||||
inputs=inputs,
|
||||
weight=w1,
|
||||
input_scale=input_scale,
|
||||
input_scale_2=input_scale_2,
|
||||
outputs=buffers["gate_up_output"],
|
||||
valid_shape_m=valid_shape_m,
|
||||
expert_layout=expert_num_tokens,
|
||||
@@ -1130,16 +1065,12 @@ class BatchedHummingGroupedExperts(HummingExpertsBase):
|
||||
tuning_config=self.w13_tuning_config_str,
|
||||
)
|
||||
|
||||
self.apply_activation(
|
||||
activation=activation,
|
||||
input=buffers["gate_up_output"],
|
||||
output=buffers["activation_output"],
|
||||
)
|
||||
|
||||
inputs, input_scale = self.quantize_input(
|
||||
inputs, input_scale, input_scale_2 = self.process_input(
|
||||
"w2",
|
||||
inputs=buffers["activation_output"],
|
||||
quanted_input=buffers.get("quanted_down_input", None),
|
||||
inputs=buffers["gate_up_output"],
|
||||
quanted_input=buffers["quanted_down_input"],
|
||||
expert_tokens=expert_num_tokens,
|
||||
activation=activation,
|
||||
)
|
||||
|
||||
self.humming_forward(
|
||||
@@ -1147,6 +1078,7 @@ class BatchedHummingGroupedExperts(HummingExpertsBase):
|
||||
inputs=inputs,
|
||||
weight=w2,
|
||||
input_scale=input_scale,
|
||||
input_scale_2=input_scale_2,
|
||||
outputs=output.view(-1, hidden_states.size(-1)),
|
||||
valid_shape_m=valid_shape_m,
|
||||
expert_layout=expert_num_tokens,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
from typing import Literal, overload
|
||||
|
||||
import torch
|
||||
|
||||
from vllm import _custom_ops as ops
|
||||
@@ -8,6 +10,48 @@ from vllm.triton_utils import triton
|
||||
from vllm.utils.math_utils import round_up
|
||||
|
||||
|
||||
@overload
|
||||
def moe_align_block_size(
|
||||
topk_ids: torch.Tensor,
|
||||
block_size: int,
|
||||
num_experts: int,
|
||||
expert_map: torch.Tensor | None = None,
|
||||
pad_sorted_ids: bool = False,
|
||||
ignore_invalid_experts: bool = False,
|
||||
*,
|
||||
return_scatter_idx: Literal[False] = False,
|
||||
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def moe_align_block_size(
|
||||
topk_ids: torch.Tensor,
|
||||
block_size: int,
|
||||
num_experts: int,
|
||||
expert_map: torch.Tensor | None = None,
|
||||
pad_sorted_ids: bool = False,
|
||||
ignore_invalid_experts: bool = False,
|
||||
*,
|
||||
return_scatter_idx: Literal[True],
|
||||
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def moe_align_block_size(
|
||||
topk_ids: torch.Tensor,
|
||||
block_size: int,
|
||||
num_experts: int,
|
||||
expert_map: torch.Tensor | None = None,
|
||||
pad_sorted_ids: bool = False,
|
||||
ignore_invalid_experts: bool = False,
|
||||
*,
|
||||
return_scatter_idx: bool,
|
||||
) -> (
|
||||
tuple[torch.Tensor, torch.Tensor, torch.Tensor]
|
||||
| tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]
|
||||
): ...
|
||||
|
||||
|
||||
def moe_align_block_size(
|
||||
topk_ids: torch.Tensor,
|
||||
block_size: int,
|
||||
@@ -15,7 +59,11 @@ def moe_align_block_size(
|
||||
expert_map: torch.Tensor | None = None,
|
||||
pad_sorted_ids: bool = False,
|
||||
ignore_invalid_experts: bool = False,
|
||||
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
return_scatter_idx: bool = False,
|
||||
) -> (
|
||||
tuple[torch.Tensor, torch.Tensor, torch.Tensor]
|
||||
| tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]
|
||||
):
|
||||
"""Aligns the token distribution across experts to be compatible with block
|
||||
size for matrix multiplication.
|
||||
|
||||
@@ -42,6 +90,8 @@ def moe_align_block_size(
|
||||
as -1. When True, all invalid expert_ids in topk_ids will be ignored
|
||||
and will not participate in counting or ranking, and there will be no
|
||||
-1 in expert_ids.
|
||||
return_scatter_idx: Whether to additionally return a masked identity
|
||||
mapping for the original routed rows.
|
||||
|
||||
Returns:
|
||||
- sorted_token_ids: A tensor containing the sorted token indices according
|
||||
@@ -49,6 +99,10 @@ def moe_align_block_size(
|
||||
- expert_ids: A tensor indicating the assigned expert index for each block.
|
||||
- num_tokens_post_padded: The total number of tokens after padding,
|
||||
ensuring divisibility by block_size.
|
||||
- scatter_idx: Only returned when return_scatter_idx=True. An int32 tensor
|
||||
shaped [topk_ids.numel(), 1], containing the original routed row index
|
||||
for valid routes and -1 otherwise. With expert_map, this requires
|
||||
ignore_invalid_experts=True.
|
||||
|
||||
This function pads the number of tokens that each expert needs to process
|
||||
so that it is divisible by block_size.
|
||||
@@ -71,6 +125,15 @@ def moe_align_block_size(
|
||||
by block_size for proper block matrix operations.
|
||||
|
||||
"""
|
||||
if return_scatter_idx and expert_map is not None and not ignore_invalid_experts:
|
||||
raise ValueError("scatter_idx with expert_map requires ignore_invalid_experts")
|
||||
scatter_idx = None
|
||||
if return_scatter_idx:
|
||||
scatter_idx = torch.empty(
|
||||
(topk_ids.numel(), 1),
|
||||
dtype=torch.int32,
|
||||
device=topk_ids.device,
|
||||
)
|
||||
max_num_tokens_padded = topk_ids.numel() + num_experts * (block_size - 1)
|
||||
if pad_sorted_ids:
|
||||
max_num_tokens_padded = round_up(max_num_tokens_padded, block_size)
|
||||
@@ -95,11 +158,14 @@ def moe_align_block_size(
|
||||
expert_ids,
|
||||
num_tokens_post_pad,
|
||||
expert_map if ignore_invalid_experts else None,
|
||||
scatter_idx,
|
||||
)
|
||||
|
||||
if expert_map is not None and not ignore_invalid_experts:
|
||||
expert_ids = expert_map[expert_ids]
|
||||
|
||||
if scatter_idx is not None:
|
||||
return sorted_ids, expert_ids, num_tokens_post_pad, scatter_idx
|
||||
return sorted_ids, expert_ids, num_tokens_post_pad
|
||||
|
||||
|
||||
|
||||
@@ -107,6 +107,37 @@ class MoEPermuteScratch:
|
||||
return topk_ids_int32
|
||||
|
||||
|
||||
def moe_prepare_scatter(
|
||||
topk_ids: torch.Tensor,
|
||||
expert_map: torch.Tensor | None,
|
||||
scratch: MoEPermuteScratch,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
"""Generate expert offsets and shared scatter/unpermute destination indices."""
|
||||
assert topk_ids.device == scratch.device
|
||||
assert topk_ids.size(0) <= scratch.max_num_tokens
|
||||
assert topk_ids.size(1) == scratch.topk
|
||||
n_token, topk = topk_ids.shape
|
||||
expanded_rows = topk_ids.numel()
|
||||
inverse = scratch.inv_permuted_idx[:expanded_rows].view(n_token, topk)
|
||||
if expanded_rows == 0:
|
||||
scratch.expert_first_token_offset.zero_()
|
||||
return scratch.expert_first_token_offset, inverse
|
||||
torch.ops._moe_C.moe_prepare_scatter(
|
||||
scratch.prepare_topk_ids(topk_ids),
|
||||
scratch.token_expert_indices_view(n_token),
|
||||
expert_map,
|
||||
scratch.num_experts,
|
||||
scratch.num_local_experts,
|
||||
scratch.expert_first_token_offset,
|
||||
inverse,
|
||||
scratch.sort_workspace,
|
||||
scratch.permuted_experts_id[:expanded_rows].view(n_token, topk),
|
||||
scratch.sorted_row_idx[:expanded_rows].view(n_token, topk),
|
||||
scratch.topk_ids_for_sort[:expanded_rows].view(n_token, topk),
|
||||
)
|
||||
return scratch.expert_first_token_offset, inverse
|
||||
|
||||
|
||||
def get_moe_permute_scratch(
|
||||
*,
|
||||
max_num_tokens: int,
|
||||
|
||||
@@ -603,7 +603,7 @@ def convert_to_fp8_moe_kernel_format(
|
||||
w13.is_shuffled = True
|
||||
w2.is_shuffled = True
|
||||
elif fp8_backend == Fp8MoeBackend.HUMMING:
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
convert_to_humming_moe_kernel_format,
|
||||
)
|
||||
|
||||
@@ -739,7 +739,7 @@ def make_fp8_moe_quant_config(
|
||||
)
|
||||
elif fp8_backend == Fp8MoeBackend.HUMMING:
|
||||
from vllm.model_executor.layers.fused_moe import RoutedExperts
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
get_humming_moe_quant_config,
|
||||
)
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ def make_int8_moe_quant_config(
|
||||
|
||||
if int8_backend == Int8MoeBackend.HUMMING:
|
||||
from vllm.model_executor.layers.fused_moe import RoutedExperts
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
get_humming_moe_quant_config,
|
||||
)
|
||||
|
||||
@@ -254,7 +254,7 @@ def convert_to_int8_moe_kernel_format(
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
"""Convert INT8 MoE weights to backend-specific kernel format."""
|
||||
if int8_backend == Int8MoeBackend.HUMMING:
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
convert_to_humming_moe_kernel_format,
|
||||
)
|
||||
|
||||
|
||||
@@ -1456,7 +1456,7 @@ def convert_to_wna16_moe_kernel_format(
|
||||
"""
|
||||
if backend == WNA16MoEBackend.HUMMING:
|
||||
from vllm.model_executor.layers.quantization.moe_wna16 import MoeWNA16Config
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
convert_to_humming_moe_kernel_format,
|
||||
)
|
||||
|
||||
|
||||
@@ -844,7 +844,7 @@ def convert_gpt_oss_weight_to_mxfp4_moe_kernel_format(
|
||||
sf_block_size = 32 # mxfp4 block size
|
||||
|
||||
if mxfp4_backend == Mxfp4MoeBackend.HUMMING:
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
convert_to_humming_moe_kernel_format,
|
||||
)
|
||||
|
||||
@@ -1428,7 +1428,7 @@ def convert_weight_to_mxfp4_moe_kernel_format(
|
||||
)
|
||||
|
||||
if mxfp4_backend == Mxfp4MoeBackend.HUMMING:
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
convert_to_humming_moe_kernel_format,
|
||||
)
|
||||
|
||||
@@ -1966,7 +1966,7 @@ def make_mxfp4_moe_quant_config(
|
||||
gemm1_clamp_limit=swiglu_limit,
|
||||
)
|
||||
elif mxfp4_backend == Mxfp4MoeBackend.HUMMING:
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
get_humming_moe_quant_config,
|
||||
)
|
||||
|
||||
|
||||
@@ -409,7 +409,7 @@ def convert_to_nvfp4_moe_kernel_format(
|
||||
is_act_and_mul=is_act_and_mul,
|
||||
)
|
||||
elif nvfp4_backend == NvFp4MoeBackend.HUMMING:
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
convert_to_humming_moe_kernel_format,
|
||||
)
|
||||
|
||||
@@ -523,7 +523,7 @@ def make_nvfp4_moe_quant_config(
|
||||
use_a16 = _use_a16(backend, use_a16)
|
||||
if backend == NvFp4MoeBackend.HUMMING:
|
||||
from vllm.model_executor.layers.fused_moe import RoutedExperts
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
get_humming_moe_quant_config,
|
||||
)
|
||||
|
||||
|
||||
@@ -713,7 +713,7 @@ class AutoAWQMoEMethod(FusedMoEMethodBase):
|
||||
|
||||
def get_fused_moe_quant_config(self, layer: RoutedExperts) -> FusedMoEQuantConfig:
|
||||
if self.wna16_moe_backend == WNA16MoEBackend.HUMMING:
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
get_humming_moe_quant_config,
|
||||
)
|
||||
|
||||
|
||||
@@ -689,7 +689,7 @@ class AutoGPTQMoEMethod(FusedMoEMethodBase):
|
||||
|
||||
def get_fused_moe_quant_config(self, layer: RoutedExperts) -> FusedMoEQuantConfig:
|
||||
if self.wna16_moe_backend == WNA16MoEBackend.HUMMING:
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
get_humming_moe_quant_config,
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -474,7 +474,7 @@ class CompressedTensorsWNA16MoEMethod(CompressedTensorsMoEMethod):
|
||||
self, layer: torch.nn.Module
|
||||
) -> FusedMoEQuantConfig | None:
|
||||
if self.wna16_backend == WNA16MoEBackend.HUMMING:
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
get_humming_moe_quant_config,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
from collections.abc import Callable
|
||||
from math import gcd
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import regex as re
|
||||
@@ -28,12 +29,15 @@ from vllm.model_executor.layers.quantization.base_config import (
|
||||
QuantizationConfig,
|
||||
QuantizeMethodBase,
|
||||
)
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
check_and_fallback_input_schema,
|
||||
convert_to_humming_moe_kernel_format,
|
||||
get_humming_linear_compute_config,
|
||||
get_humming_moe_quant_config,
|
||||
humming_update_schema_hadamard_block_size,
|
||||
input_schema_to_quant_key,
|
||||
make_humming_moe_kernel,
|
||||
resolve_humming_layer_config,
|
||||
select_humming_moe_experts,
|
||||
weight_schema_to_quant_key,
|
||||
)
|
||||
@@ -227,23 +231,13 @@ class HummingConfig(QuantizationConfig):
|
||||
return None
|
||||
config = group_config
|
||||
|
||||
layer_config = config
|
||||
layer_dynamic = config.get("dynamic", {})
|
||||
if not isinstance(layer_dynamic, dict):
|
||||
layer_dynamic = {}
|
||||
for regex, override_config in layer_dynamic.items():
|
||||
if regex[:1] != "+":
|
||||
continue
|
||||
if re.match(regex[2:], prefix):
|
||||
layer_config = config.copy()
|
||||
layer_config.update(override_config)
|
||||
break
|
||||
layer_config = resolve_humming_layer_config(config, prefix)
|
||||
|
||||
if "quant_method" in layer_config:
|
||||
return _hm.BaseWeightSchema.from_config(layer_config)
|
||||
return None
|
||||
|
||||
def get_layer_input_schema(self, config: dict[str, Any], prefix: str):
|
||||
def get_layer_input_config(self, config: dict[str, Any], prefix: str):
|
||||
if self.is_layer_skipped(config, prefix):
|
||||
return None
|
||||
if config["quant_method"] in ["compressed-tensors", "modelopt"]:
|
||||
@@ -252,10 +246,15 @@ class HummingConfig(QuantizationConfig):
|
||||
return None
|
||||
config = group_config
|
||||
|
||||
config = resolve_humming_layer_config(config, prefix)
|
||||
if config.get("quant_method", None) in _hm.BaseInputSchema.INPUT_SCHEMA_MAP:
|
||||
return _hm.BaseInputSchema.from_config(config)
|
||||
return config
|
||||
return None
|
||||
|
||||
def get_layer_input_schema(self, config: dict[str, Any], prefix: str):
|
||||
config = self.get_layer_input_config(config, prefix)
|
||||
return None if config is None else _hm.BaseInputSchema.from_config(config)
|
||||
|
||||
def get_quant_config_for_layer(
|
||||
self, prefix: str, layer_type: str
|
||||
) -> "HummingLayerQuantizationConfig | None":
|
||||
@@ -279,14 +278,25 @@ class HummingConfig(QuantizationConfig):
|
||||
if weight_schema is not None:
|
||||
input_schema = None
|
||||
force_input_schema = None
|
||||
allow_input_schema_fallback = True
|
||||
|
||||
if self.full_config:
|
||||
input_schema = self.get_layer_input_schema(self.full_config, prefix)
|
||||
input_config = self.get_layer_input_config(self.full_config, prefix)
|
||||
if input_config is not None:
|
||||
allow_input_schema_fallback = input_config.pop(
|
||||
"allow_fallback", True
|
||||
)
|
||||
input_schema = _hm.BaseInputSchema.from_config(input_config)
|
||||
|
||||
if envs.VLLM_HUMMING_INPUT_QUANT_CONFIG:
|
||||
quant_config = envs.VLLM_HUMMING_INPUT_QUANT_CONFIG.copy()
|
||||
quant_config["quant_method"] = "humming"
|
||||
force_input_schema = self.get_layer_input_schema(quant_config, prefix)
|
||||
input_config = self.get_layer_input_config(quant_config, prefix)
|
||||
if input_config is not None:
|
||||
allow_input_schema_fallback = input_config.pop(
|
||||
"allow_fallback", False
|
||||
)
|
||||
force_input_schema = _hm.BaseInputSchema.from_config(input_config)
|
||||
if input_schema is None:
|
||||
input_schema = force_input_schema
|
||||
|
||||
@@ -299,6 +309,7 @@ class HummingConfig(QuantizationConfig):
|
||||
force_weight_schema=force_weight_schema,
|
||||
force_input_schema=force_input_schema,
|
||||
is_online_quant=is_online_quant,
|
||||
allow_input_schema_fallback=allow_input_schema_fallback,
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -318,6 +329,11 @@ class HummingConfig(QuantizationConfig):
|
||||
elif isinstance(layer, LinearBase):
|
||||
return UnquantizedLinearMethod()
|
||||
elif isinstance(layer, LinearBase):
|
||||
input_size = layer.input_size
|
||||
partition_size = getattr(layer, "input_size_per_partition", input_size)
|
||||
is_online_quant = quant_config.is_online_quant
|
||||
if is_online_quant and (input_size % 32 != 0 or partition_size % 32 != 0):
|
||||
return UnquantizedLinearMethod()
|
||||
return HummingLinearMethod(quant_config)
|
||||
elif isinstance(layer, RoutedExperts):
|
||||
return HummingMoEMethod(quant_config, layer.moe_config)
|
||||
@@ -332,6 +348,7 @@ class HummingLayerQuantizationConfig(HummingConfig):
|
||||
force_weight_schema: "HummingWeightSchema | None" = None,
|
||||
force_input_schema: "HummingInputSchema | None" = None,
|
||||
is_online_quant: bool = False,
|
||||
allow_input_schema_fallback: bool = True,
|
||||
):
|
||||
self.weight_schema = weight_schema
|
||||
if input_schema is None:
|
||||
@@ -340,6 +357,7 @@ class HummingLayerQuantizationConfig(HummingConfig):
|
||||
self.force_weight_schema = force_weight_schema
|
||||
self.force_input_schema = force_input_schema
|
||||
self.is_online_quant = is_online_quant
|
||||
self.allow_input_schema_fallback = allow_input_schema_fallback
|
||||
|
||||
@classmethod
|
||||
def from_config(cls, config):
|
||||
@@ -373,21 +391,13 @@ class HummingLinearMethod(LinearMethodBase):
|
||||
if is_unquantized and self.is_online_quant:
|
||||
# online quant (fp16/bf16 -> quant_type)
|
||||
assert isinstance(self.weight_schema, _hm.HummingWeightSchema)
|
||||
f16_dtype = _hm.DataType.from_torch_dtype(layer.param_dtype)
|
||||
has_global_scale = "TENSOR" in str(self.weight_schema.weight_scale_type)
|
||||
tensor_list = _hm.quantize_weight(
|
||||
weight=loaded_weight,
|
||||
dtype=self.weight_schema.b_dtype,
|
||||
scale_dtype=self.weight_schema.bs_dtype or f16_dtype,
|
||||
group_size=self.weight_schema.weight_scale_group_size,
|
||||
has_zero_point=self.weight_schema.has_zero_point,
|
||||
has_global_scale=has_global_scale,
|
||||
is_fp_zero_point=self.weight_schema.is_fp_zero_point,
|
||||
pack=True,
|
||||
tensors = self.weight_schema.quant_tensor(
|
||||
loaded_weight.to(param.device),
|
||||
self.weight_schema,
|
||||
layer.param_dtype,
|
||||
)
|
||||
|
||||
key_list = ["weight", "weight_scale", "zero_point", "global_scale"]
|
||||
for key, tensor in zip(key_list, tensor_list):
|
||||
for key, tensor in tensors.items():
|
||||
if tensor is None or tensor.nelement() == 0:
|
||||
continue
|
||||
param = getattr(layer, key)
|
||||
@@ -459,6 +469,18 @@ class HummingLinearMethod(LinearMethodBase):
|
||||
layer.output_partition_sizes = output_partition_sizes
|
||||
layer.extra_weight_attrs = extra_weight_attrs.copy()
|
||||
|
||||
input_schema = self.force_input_schema or self.input_schema
|
||||
input_schema = input_schema.to_humming_schema(params_dtype)
|
||||
for name in ("weight_schema", "force_weight_schema"):
|
||||
schema = getattr(self, name)
|
||||
if getattr(schema, "hadamard_block_size", None) == -1:
|
||||
schema = humming_update_schema_hadamard_block_size(
|
||||
weight_schema=schema,
|
||||
input_schema=input_schema,
|
||||
shape_k=input_size_per_partition,
|
||||
)
|
||||
setattr(self, name, schema)
|
||||
|
||||
weight_loader = extra_weight_attrs.get("weight_loader", default_weight_loader)
|
||||
new_weight_loader = self.prepare_weight_loader(layer, weight_loader)
|
||||
extra_weight_attrs["weight_loader"] = new_weight_loader
|
||||
@@ -502,7 +524,10 @@ class HummingLinearMethod(LinearMethodBase):
|
||||
return None
|
||||
|
||||
# convert from checkpoint format to humming format
|
||||
if not isinstance(self.weight_schema, _hm.HummingWeightSchema):
|
||||
is_humming_weight = isinstance(self.weight_schema, _hm.HummingWeightSchema)
|
||||
is_humming_input = isinstance(self.input_schema, _hm.HummingInputSchema)
|
||||
|
||||
if not is_humming_weight or not is_humming_input:
|
||||
self.weight_schema, tensors = self.weight_schema.convert_humming(
|
||||
tensors=layer.state_dict(),
|
||||
shape_n_stacks=layer.output_partition_sizes,
|
||||
@@ -510,12 +535,13 @@ class HummingLinearMethod(LinearMethodBase):
|
||||
param_dtype=layer.param_dtype,
|
||||
)
|
||||
|
||||
self.input_schema, _ = self.input_schema.convert_humming(
|
||||
self.input_schema, input_tensors = self.input_schema.convert_humming(
|
||||
tensors=layer.state_dict(),
|
||||
shape_n_stacks=layer.output_partition_sizes,
|
||||
shape_k_stacks=[layer.input_size_per_partition],
|
||||
param_dtype=layer.param_dtype,
|
||||
)
|
||||
tensors.update(input_tensors)
|
||||
|
||||
for name, _ in list(layer.named_parameters()):
|
||||
delattr(layer, name)
|
||||
@@ -530,11 +556,15 @@ class HummingLinearMethod(LinearMethodBase):
|
||||
assert isinstance(self.weight_schema, _hm.HummingWeightSchema)
|
||||
force_requant = self.force_weight_schema is not None
|
||||
if force_requant and self.weight_schema != self.force_weight_schema:
|
||||
source_tensors = layer.state_dict()
|
||||
tensors = self.weight_schema.requant_tensors(
|
||||
tensors=layer.state_dict(),
|
||||
tensors=source_tensors,
|
||||
target_weight_schema=self.force_weight_schema,
|
||||
param_dtype=layer.param_dtype,
|
||||
)
|
||||
name = self.input_schema.static_tensor_scale_name
|
||||
if name is not None:
|
||||
tensors[name] = source_tensors[name]
|
||||
|
||||
self.weight_schema = self.force_weight_schema
|
||||
|
||||
@@ -548,6 +578,12 @@ class HummingLinearMethod(LinearMethodBase):
|
||||
|
||||
del tensors
|
||||
|
||||
self.input_schema = check_and_fallback_input_schema(
|
||||
weight_schema=self.weight_schema,
|
||||
input_schema=self.input_schema,
|
||||
param_dtype=layer.param_dtype,
|
||||
allow_fallback=self.quant_config.allow_input_schema_fallback,
|
||||
)
|
||||
self.layer_config = _hm.prepare_layer_config(
|
||||
shape_n=layer.output_partition_sizes_sum,
|
||||
shape_k=layer.input_size_per_partition,
|
||||
@@ -557,10 +593,16 @@ class HummingLinearMethod(LinearMethodBase):
|
||||
pad_k_to_multiple=128,
|
||||
has_bias=layer.has_bias,
|
||||
torch_dtype=layer.param_dtype,
|
||||
device=layer.weight.device,
|
||||
)
|
||||
tensors = _hm.transform_humming_tensors(
|
||||
self.layer_config, dict(layer.named_parameters())
|
||||
)
|
||||
source_tensors = dict(layer.named_parameters())
|
||||
tensors = _hm.transform_humming_tensors(self.layer_config, source_tensors)
|
||||
name = self.input_schema.static_tensor_scale_name
|
||||
if name is not None:
|
||||
tensors[name] = source_tensors[name]
|
||||
if "bias" in tensors:
|
||||
zero_bias = torch.zeros_like(tensors["bias"])
|
||||
layer.register_buffer("zero_bias", zero_bias)
|
||||
for name, _ in list(layer.named_parameters()):
|
||||
delattr(layer, name)
|
||||
for name, tensor in tensors.items():
|
||||
@@ -583,8 +625,11 @@ class HummingLinearMethod(LinearMethodBase):
|
||||
weight=layer.weight,
|
||||
weight_scale=getattr(layer, "weight_scale", None),
|
||||
zero_point=getattr(layer, "zero_point", None),
|
||||
bias=getattr(layer, "bias", None),
|
||||
bias=getattr(layer, "bias" if bias is not None else "zero_bias", None),
|
||||
weight_scale_2=getattr(layer, "weight_scale_2", None),
|
||||
input_scale=getattr(layer, "input_scale", None),
|
||||
input_scale_2=getattr(layer, "input_scale_2", None),
|
||||
hadamard_block_size=self.weight_schema.hadamard_block_size,
|
||||
locks=self.locks,
|
||||
compute_config=self.compute_config,
|
||||
)
|
||||
@@ -606,11 +651,15 @@ class HummingMoEMethod(FusedMoEMethodBase):
|
||||
# Derive QuantKeys from humming schemas.
|
||||
# Prefer force schemas (the final format after requant) over base.
|
||||
weight_key = weight_schema_to_quant_key(
|
||||
self.force_weight_schema or self.weight_schema
|
||||
self.force_weight_schema or self.weight_schema, moe.in_dtype
|
||||
)
|
||||
activation_key = input_schema_to_quant_key(
|
||||
self.force_input_schema or self.input_schema
|
||||
runtime_input_schema = check_and_fallback_input_schema(
|
||||
weight_schema=self.force_weight_schema or self.weight_schema,
|
||||
input_schema=self.force_input_schema or self.input_schema,
|
||||
param_dtype=moe.in_dtype,
|
||||
allow_fallback=quant_config.allow_input_schema_fallback,
|
||||
)
|
||||
activation_key = input_schema_to_quant_key(runtime_input_schema, moe.in_dtype)
|
||||
|
||||
# Select Humming MoE experts
|
||||
self.experts_cls = select_humming_moe_experts(
|
||||
@@ -634,22 +683,14 @@ class HummingMoEMethod(FusedMoEMethodBase):
|
||||
# online quant (fp16/bf16 -> quant_type)
|
||||
if is_unquantized:
|
||||
assert isinstance(self.weight_schema, _hm.HummingWeightSchema)
|
||||
f16_dtype = _hm.DataType.from_torch_dtype(layer.param_dtype)
|
||||
has_global_scale = "TENSOR" in str(self.weight_schema.weight_scale_type)
|
||||
tensor_list = _hm.quantize_weight(
|
||||
weight=loaded_weight,
|
||||
dtype=self.weight_schema.b_dtype,
|
||||
scale_dtype=self.weight_schema.bs_dtype or f16_dtype,
|
||||
group_size=self.weight_schema.weight_scale_group_size,
|
||||
has_zero_point=self.weight_schema.has_zero_point,
|
||||
has_global_scale=has_global_scale,
|
||||
is_fp_zero_point=self.weight_schema.is_fp_zero_point,
|
||||
pack=True,
|
||||
tensors = self.weight_schema.quant_tensor(
|
||||
loaded_weight.to(param.device),
|
||||
self.weight_schema,
|
||||
layer.param_dtype,
|
||||
)
|
||||
|
||||
key_list = ["weight", "weight_scale", "zero_point", "global_scale"]
|
||||
success = True
|
||||
for key, tensor in zip(key_list, tensor_list):
|
||||
for key, tensor in tensors.items():
|
||||
if tensor is None or tensor.nelement() == 0:
|
||||
continue
|
||||
sublayer_name = "w2" if shard_id == "w2" else "w13"
|
||||
@@ -695,6 +736,18 @@ class HummingMoEMethod(FusedMoEMethodBase):
|
||||
layer.num_experts = num_experts
|
||||
layer.param_dtype = params_dtype
|
||||
layer.intermediate_size = intermediate_size_per_partition
|
||||
input_schema = self.force_input_schema or self.input_schema
|
||||
input_schema = input_schema.to_humming_schema(params_dtype)
|
||||
shape_k = gcd(hidden_size, intermediate_size_per_partition)
|
||||
for name in ("weight_schema", "force_weight_schema"):
|
||||
schema = getattr(self, name)
|
||||
if getattr(schema, "hadamard_block_size", None) == -1:
|
||||
schema = humming_update_schema_hadamard_block_size(
|
||||
weight_schema=schema,
|
||||
input_schema=input_schema,
|
||||
shape_k=shape_k,
|
||||
)
|
||||
setattr(self, name, schema)
|
||||
weight_loader = extra_weight_attrs.get("weight_loader", default_weight_loader)
|
||||
weight_loader = self.prepare_weight_loader(layer, weight_loader)
|
||||
extra_weight_attrs["weight_loader"] = weight_loader
|
||||
@@ -761,6 +814,7 @@ class HummingMoEMethod(FusedMoEMethodBase):
|
||||
weight_schema=self.weight_schema,
|
||||
input_schema=self.input_schema,
|
||||
force_weight_schema=self.force_weight_schema,
|
||||
allow_input_schema_fallback=self.quant_config.allow_input_schema_fallback,
|
||||
)
|
||||
|
||||
# Build the MoE kernel
|
||||
|
||||
@@ -389,7 +389,7 @@ class MoeWNA16Method(FusedMoEMethodBase):
|
||||
self, layer: RoutedExperts
|
||||
) -> FusedMoEQuantConfig | None:
|
||||
if self.wna16_backend == WNA16MoEBackend.HUMMING:
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
from vllm.model_executor.layers.quantization.utils.humming import (
|
||||
get_humming_moe_quant_config,
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Humming quantization integration."""
|
||||
|
||||
from vllm.model_executor.layers.quantization.utils.humming.activation import (
|
||||
get_humming_activation,
|
||||
)
|
||||
from vllm.model_executor.layers.quantization.utils.humming.linear import (
|
||||
apply_humming_linear,
|
||||
convert_linear_layer_to_humming_standard,
|
||||
get_humming_linear_compute_config,
|
||||
prepare_humming_linear_layer_config,
|
||||
)
|
||||
from vllm.model_executor.layers.quantization.utils.humming.moe import (
|
||||
HummingMoEQuantConfig,
|
||||
convert_to_humming_moe_kernel_format,
|
||||
get_humming_moe_quant_config,
|
||||
make_humming_moe_kernel,
|
||||
make_humming_moe_quant_config,
|
||||
select_humming_moe_experts,
|
||||
)
|
||||
from vllm.model_executor.layers.quantization.utils.humming.schema import (
|
||||
check_and_fallback_input_schema,
|
||||
humming_is_layer_skipped,
|
||||
humming_update_schema_hadamard_block_size,
|
||||
input_schema_to_quant_key,
|
||||
quant_key_to_input_schema,
|
||||
resolve_humming_layer_config,
|
||||
weight_schema_to_quant_key,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"check_and_fallback_input_schema",
|
||||
"get_humming_activation",
|
||||
"HummingMoEQuantConfig",
|
||||
"get_humming_moe_quant_config",
|
||||
"humming_is_layer_skipped",
|
||||
"humming_update_schema_hadamard_block_size",
|
||||
"make_humming_moe_quant_config",
|
||||
"apply_humming_linear",
|
||||
"convert_linear_layer_to_humming_standard",
|
||||
"get_humming_linear_compute_config",
|
||||
"prepare_humming_linear_layer_config",
|
||||
"make_humming_moe_kernel",
|
||||
"select_humming_moe_experts",
|
||||
"input_schema_to_quant_key",
|
||||
"quant_key_to_input_schema",
|
||||
"resolve_humming_layer_config",
|
||||
"weight_schema_to_quant_key",
|
||||
"convert_to_humming_moe_kernel_format",
|
||||
]
|
||||
@@ -0,0 +1,126 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""MoE activation expressions for Humming input processing."""
|
||||
|
||||
from dataclasses import replace
|
||||
|
||||
from vllm.model_executor.layers.fused_moe.activation import (
|
||||
ApplyMoEActivationConfig,
|
||||
MoEActivation,
|
||||
)
|
||||
|
||||
_SILU_IMPL = "a / (1.f + expf(-a))"
|
||||
_GELU_IMPL = "0.5f * a * (1.f + erff(a * 0.7071067811865476f))"
|
||||
_RELU2_IMPL = "fmaxf(a, 0.f) * fmaxf(a, 0.f)"
|
||||
_GELU_TANH_IMPL = """[](float a) {
|
||||
const float cubic = 0.044715f * a * a * a;
|
||||
const float inner = 0.7978845608028654f * (a + cubic);
|
||||
return 0.5f * a * (1.f + tanhf(inner));
|
||||
}(a)"""
|
||||
_CLAMPED_SILU_IMPL = """[](float a, float b) {{
|
||||
const float gate = fminf(a, {limit});
|
||||
const float up = fmaxf(fminf(b, {limit}), -{limit});
|
||||
const float activated_gate = gate / (1.f + expf(-{alpha} * gate));
|
||||
return activated_gate * (up + {beta});
|
||||
}}(a, b)"""
|
||||
|
||||
# Parameterized expressions are resolved by get_humming_activation().
|
||||
_HUMMING_ACTIVATIONS: dict[MoEActivation, dict[str, str]] = {
|
||||
MoEActivation.SILU: {
|
||||
"activation_type": "binary_split",
|
||||
"activation_impl": f"({_SILU_IMPL}) * b",
|
||||
},
|
||||
MoEActivation.GELU: {
|
||||
"activation_type": "binary_split",
|
||||
"activation_impl": f"({_GELU_IMPL}) * b",
|
||||
},
|
||||
MoEActivation.GELU_TANH: {
|
||||
"activation_type": "binary_split",
|
||||
"activation_impl": f"({_GELU_TANH_IMPL}) * b",
|
||||
},
|
||||
MoEActivation.RELU2: {
|
||||
"activation_type": "binary_split",
|
||||
"activation_impl": f"({_RELU2_IMPL}) * b",
|
||||
},
|
||||
MoEActivation.SWIGLUOAI: {
|
||||
"activation_type": "binary_interleaved",
|
||||
"activation_impl": _CLAMPED_SILU_IMPL,
|
||||
},
|
||||
MoEActivation.SITU: {
|
||||
"activation_type": "binary_split",
|
||||
"activation_impl": """[](float a, float b) {{
|
||||
const float gate = {situ_beta} * tanhf(a / {situ_beta});
|
||||
const float activated_gate = gate / (1.f + expf(-a));
|
||||
const float up = {situ_up};
|
||||
return activated_gate * up;
|
||||
}}(a, b)""",
|
||||
},
|
||||
MoEActivation.SWIGLUOAI_UNINTERLEAVE: {
|
||||
"activation_type": "binary_split",
|
||||
"activation_impl": _CLAMPED_SILU_IMPL,
|
||||
},
|
||||
MoEActivation.SWIGLUSTEP: {
|
||||
"activation_type": "binary_split",
|
||||
"activation_impl": """[](float a, float b) {
|
||||
const float activated_gate = a / (1.f + expf(-a));
|
||||
const float gate = fminf(activated_gate, 7.f);
|
||||
const float up = fmaxf(fminf(b, 7.f), -7.f);
|
||||
return gate * up;
|
||||
}(a, b)""",
|
||||
},
|
||||
MoEActivation.SILU_NO_MUL: {
|
||||
"activation_type": "unary",
|
||||
"activation_impl": _SILU_IMPL,
|
||||
},
|
||||
MoEActivation.GELU_NO_MUL: {
|
||||
"activation_type": "unary",
|
||||
"activation_impl": _GELU_IMPL,
|
||||
},
|
||||
MoEActivation.GELU_TANH_NO_MUL: {
|
||||
"activation_type": "unary",
|
||||
"activation_impl": _GELU_TANH_IMPL,
|
||||
},
|
||||
MoEActivation.RELU2_NO_MUL: {
|
||||
"activation_type": "unary",
|
||||
"activation_impl": _RELU2_IMPL,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_humming_activation(
|
||||
activation: MoEActivation,
|
||||
config: ApplyMoEActivationConfig | None = None,
|
||||
) -> dict[str, str]:
|
||||
config = config or ApplyMoEActivationConfig()
|
||||
use_clamped_silu = False
|
||||
if activation == MoEActivation.SWIGLUOAI:
|
||||
config = ApplyMoEActivationConfig(clamp_limit=7.0, alpha=1.702, beta=1.0)
|
||||
use_clamped_silu = True
|
||||
elif activation == MoEActivation.SILU and config.clamp_limit is not None:
|
||||
config = replace(config, alpha=1.0, beta=0.0)
|
||||
use_clamped_silu = True
|
||||
elif activation == MoEActivation.SWIGLUOAI_UNINTERLEAVE:
|
||||
if config.clamp_limit is None:
|
||||
raise ValueError("SWIGLUOAI_UNINTERLEAVE requires clamp_limit")
|
||||
use_clamped_silu = True
|
||||
|
||||
definition = _HUMMING_ACTIVATIONS[activation].copy()
|
||||
if activation == MoEActivation.SITU:
|
||||
if config.activation_situ_beta is None:
|
||||
raise ValueError("SITU requires activation_situ_beta")
|
||||
linear_beta = config.activation_situ_linear_beta
|
||||
situ_up = "b"
|
||||
if linear_beta is not None and linear_beta > 0:
|
||||
situ_up = f"({float(linear_beta)}f) * tanhf(b / ({float(linear_beta)}f))"
|
||||
situ_beta = f"({float(config.activation_situ_beta)}f)"
|
||||
activation_impl = definition["activation_impl"]
|
||||
activation_impl = activation_impl.format(situ_beta=situ_beta, situ_up=situ_up)
|
||||
definition["activation_impl"] = activation_impl
|
||||
elif use_clamped_silu:
|
||||
assert config.clamp_limit is not None
|
||||
limit = f"({float(config.clamp_limit)}f)"
|
||||
alpha = f"({float(config.alpha)}f)"
|
||||
beta = f"({float(config.beta)}f)"
|
||||
activation_impl = _CLAMPED_SILU_IMPL.format(limit=limit, alpha=alpha, beta=beta)
|
||||
definition["activation_impl"] = activation_impl
|
||||
return definition
|
||||
@@ -0,0 +1,183 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Prepare and execute Humming linear layers."""
|
||||
|
||||
import json
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
|
||||
from vllm import envs
|
||||
from vllm.model_executor.layers.linear import LinearBase
|
||||
from vllm.model_executor.layers.quantization.utils.humming.schema import (
|
||||
check_and_fallback_input_schema,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from vllm.utils.humming import BaseInputSchema, LayerConfig
|
||||
|
||||
|
||||
def convert_linear_layer_to_humming_standard(
|
||||
layer: LinearBase, name_map: dict[str, str]
|
||||
):
|
||||
"""Rename/reshape a linear layer's quantized params (the canonical MPLinear
|
||||
layout: ``weight_packed`` int32 + ``weight_scale``) into the parameter names
|
||||
and layout humming's weight schema expects (``weight`` / ``weight_scale``)."""
|
||||
for name, checkpoint_name in name_map.items():
|
||||
tensor = getattr(layer, checkpoint_name)
|
||||
delattr(layer, checkpoint_name)
|
||||
|
||||
if name == "weight":
|
||||
input_dim = getattr(tensor, "input_dim", 1)
|
||||
output_dim = getattr(tensor, "output_dim", 0)
|
||||
|
||||
if input_dim == 0 and output_dim == 1:
|
||||
tensor = tensor.transpose(1, 0).contiguous()
|
||||
else:
|
||||
assert output_dim == 0 and input_dim == 1
|
||||
|
||||
tensor = tensor.view(tensor.size(0), -1).view(torch.int32)
|
||||
elif name in ["weight_scale", "zero_point"]:
|
||||
if getattr(tensor, "output_dim", 0) == 1:
|
||||
tensor = tensor.transpose(0, 1).contiguous()
|
||||
if tensor.ndim == 1:
|
||||
tensor = tensor.unsqueeze(1)
|
||||
|
||||
tensor = tensor.view(torch.int32) if name == "zero_point" else tensor
|
||||
|
||||
if isinstance(tensor, torch.nn.Parameter):
|
||||
param = tensor
|
||||
else:
|
||||
param = torch.nn.Parameter(tensor, requires_grad=False)
|
||||
|
||||
setattr(layer, name, param)
|
||||
|
||||
|
||||
def prepare_humming_linear_layer_config(
|
||||
layer: LinearBase,
|
||||
quant_config: dict,
|
||||
input_quant_config: dict | None = None,
|
||||
*,
|
||||
input_schema: "BaseInputSchema | None" = None,
|
||||
) -> "LayerConfig":
|
||||
from vllm.utils.humming import (
|
||||
BaseInputSchema,
|
||||
BaseWeightSchema,
|
||||
HummingInputSchema,
|
||||
prepare_layer_config,
|
||||
transform_humming_tensors,
|
||||
)
|
||||
|
||||
weight_schema = BaseWeightSchema.from_config(quant_config)
|
||||
if input_schema is None:
|
||||
if input_quant_config is not None:
|
||||
input_schema = BaseInputSchema.from_config(input_quant_config)
|
||||
else:
|
||||
input_schema = HummingInputSchema()
|
||||
|
||||
# ReplicatedLinear has no TP partitioning and so does not set
|
||||
# input_size_per_partition; for it that is just input_size. Use hasattr
|
||||
# rather than getattr's default arg, which is evaluated eagerly and would
|
||||
# raise on layers lacking input_size (e.g. ParallelLMHead).
|
||||
if hasattr(layer, "input_size_per_partition"):
|
||||
input_size_per_partition = layer.input_size_per_partition
|
||||
else:
|
||||
input_size_per_partition = layer.input_size
|
||||
shape_k_stacks = [input_size_per_partition]
|
||||
shape_n_stacks = layer.output_partition_sizes
|
||||
|
||||
# Step 1: convert weight and input schemas to humming standard format
|
||||
source_tensors = dict(layer.named_parameters())
|
||||
weight_schema, tensors = weight_schema.convert_humming(
|
||||
tensors=source_tensors,
|
||||
shape_n_stacks=shape_n_stacks,
|
||||
shape_k_stacks=shape_k_stacks,
|
||||
param_dtype=layer.params_dtype,
|
||||
)
|
||||
input_schema, input_tensors = input_schema.convert_humming(
|
||||
tensors=source_tensors,
|
||||
shape_n_stacks=shape_n_stacks,
|
||||
shape_k_stacks=shape_k_stacks,
|
||||
param_dtype=layer.params_dtype,
|
||||
)
|
||||
|
||||
tensors.update(input_tensors)
|
||||
layer.weight_schema = weight_schema
|
||||
input_schema = check_and_fallback_input_schema(
|
||||
weight_schema=weight_schema,
|
||||
input_schema=input_schema,
|
||||
param_dtype=layer.params_dtype,
|
||||
)
|
||||
for name in ("input_scale", "input_scale_2"):
|
||||
if name != input_schema.static_tensor_scale_name:
|
||||
tensors.pop(name, None)
|
||||
input_tensors.pop(name, None)
|
||||
|
||||
# Step 2: transform weight (humming standard format) for forwarding.
|
||||
config = prepare_layer_config(
|
||||
shape_n=sum(layer.output_partition_sizes),
|
||||
shape_k=input_size_per_partition,
|
||||
weight_schema=weight_schema,
|
||||
input_schema=input_schema,
|
||||
pad_n_to_multiple=256,
|
||||
pad_k_to_multiple=128,
|
||||
has_bias=layer.has_bias,
|
||||
torch_dtype=layer.params_dtype,
|
||||
device=tensors["weight"].device,
|
||||
)
|
||||
tensors = transform_humming_tensors(config, tensors)
|
||||
tensors.update(input_tensors)
|
||||
if "bias" in tensors:
|
||||
zero_bias = torch.zeros_like(tensors["bias"])
|
||||
layer.register_buffer("zero_bias", zero_bias)
|
||||
for name, _ in list(layer.named_parameters()):
|
||||
delattr(layer, name)
|
||||
for name, tensor in tensors.items():
|
||||
param = torch.nn.Parameter(tensor, requires_grad=False)
|
||||
setattr(layer, name, param)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def get_humming_linear_compute_config() -> str:
|
||||
return json.dumps(
|
||||
{
|
||||
"use_batch_invariant": envs.VLLM_BATCH_INVARIANT,
|
||||
"use_f16_accum": envs.VLLM_HUMMING_USE_F16_ACCUM,
|
||||
"gemm_type": "dense",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def apply_humming_linear(
|
||||
layer: LinearBase,
|
||||
x: torch.Tensor,
|
||||
*,
|
||||
skip_bias_add: bool = False,
|
||||
layer_config: "LayerConfig",
|
||||
compute_config: str,
|
||||
locks: torch.Tensor,
|
||||
) -> torch.Tensor:
|
||||
from vllm.utils.humming import humming_forward
|
||||
|
||||
mode = layer_config.input_quant_mode
|
||||
input_scale = getattr(layer, "input_scale", None) if mode.should_quantize else None
|
||||
input_scale_2 = (
|
||||
getattr(layer, "input_scale_2", None) if mode.has_secondary_scale else None
|
||||
)
|
||||
flatten_inputs = x.reshape(-1, x.size(-1))
|
||||
output = humming_forward(
|
||||
layer_config,
|
||||
inputs=flatten_inputs,
|
||||
weight=layer.weight,
|
||||
weight_scale=getattr(layer, "weight_scale", None),
|
||||
zero_point=getattr(layer, "zero_point", None),
|
||||
bias=getattr(layer, "zero_bias" if skip_bias_add else "bias", None),
|
||||
weight_scale_2=getattr(layer, "weight_scale_2", None),
|
||||
input_scale=input_scale,
|
||||
input_scale_2=input_scale_2,
|
||||
hadamard_block_size=layer.weight_schema.hadamard_block_size,
|
||||
locks=locks,
|
||||
compute_config=compute_config,
|
||||
)
|
||||
return output.view(*x.shape[:-1], output.size(-1))
|
||||
+65
-539
@@ -1,13 +1,14 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
import json
|
||||
"""Configure, prepare weights for, and assemble Humming MoE kernels."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import regex as re
|
||||
import torch
|
||||
|
||||
import vllm.model_executor.layers.fused_moe.modular_kernel as mk
|
||||
import vllm.model_executor.layers.quantization.utils.humming.schema as humming_schema
|
||||
from vllm import envs
|
||||
from vllm.logger import init_logger
|
||||
from vllm.model_executor.layers.fused_moe.all2all_utils import (
|
||||
@@ -23,34 +24,19 @@ from vllm.model_executor.layers.fused_moe.experts.fused_humming_moe import (
|
||||
HummingGroupedExperts,
|
||||
HummingIndexedExperts,
|
||||
)
|
||||
from vllm.model_executor.layers.linear import LinearBase
|
||||
from vllm.model_executor.layers.quantization.utils.humming.schema import (
|
||||
humming_is_layer_skipped,
|
||||
)
|
||||
from vllm.model_executor.layers.quantization.utils.quant_utils import (
|
||||
FP4_DTYPE,
|
||||
FP8_DTYPE,
|
||||
INT4_DTYPE,
|
||||
INT8_DTYPE,
|
||||
MXFP_SCALE_DTYPE,
|
||||
GroupShape,
|
||||
QuantKey,
|
||||
ScaleDesc,
|
||||
)
|
||||
from vllm.utils.import_utils import has_humming
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from vllm.model_executor.layers.fused_moe.routed_experts import RoutedExperts
|
||||
from vllm.utils.humming import (
|
||||
AWQWeightSchema,
|
||||
BaseInputSchema,
|
||||
BaseWeightSchema,
|
||||
CompressedTensorsInputSchema,
|
||||
CompressedTensorsWeightSchema,
|
||||
Fp8WeightSchema,
|
||||
GPTQWeightSchema,
|
||||
HummingInputSchema,
|
||||
HummingWeightSchema,
|
||||
LayerConfig,
|
||||
)
|
||||
from vllm.utils.humming import dtypes as humming_dtypes
|
||||
from vllm.utils.humming import LayerConfig
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
@@ -59,504 +45,12 @@ logger = init_logger(__name__)
|
||||
class HummingMoEQuantConfig(FusedMoEQuantConfig):
|
||||
w1_humming_config: "LayerConfig"
|
||||
w2_humming_config: "LayerConfig"
|
||||
|
||||
|
||||
if has_humming():
|
||||
from vllm.utils.humming import dtypes as humming_dtypes
|
||||
|
||||
_HUMMING_TO_QUANT_DTYPE: dict[humming_dtypes.DataType, Any] = {
|
||||
humming_dtypes.float4e2m1: FP4_DTYPE,
|
||||
humming_dtypes.float8e4m3: FP8_DTYPE,
|
||||
humming_dtypes.float8e5m2: torch.float8_e5m2,
|
||||
humming_dtypes.int8: torch.int8,
|
||||
humming_dtypes.uint4: INT4_DTYPE,
|
||||
humming_dtypes.uint8: INT8_DTYPE,
|
||||
humming_dtypes.uint2: torch.uint8,
|
||||
humming_dtypes.uint3: torch.uint8,
|
||||
humming_dtypes.uint5: torch.uint8,
|
||||
humming_dtypes.uint6: torch.uint8,
|
||||
humming_dtypes.uint7: torch.uint8,
|
||||
}
|
||||
|
||||
_HUMMING_TO_SCALE_DTYPE: dict[humming_dtypes.DataType, torch.dtype] = {
|
||||
humming_dtypes.float8e8m0: MXFP_SCALE_DTYPE,
|
||||
humming_dtypes.float8e4m3: FP8_DTYPE,
|
||||
humming_dtypes.float16: torch.float16,
|
||||
humming_dtypes.bfloat16: torch.bfloat16,
|
||||
humming_dtypes.float32: torch.float32,
|
||||
}
|
||||
|
||||
|
||||
def _group_shape(group_size: int, group_size_n: int = 0) -> GroupShape:
|
||||
"""Map humming group sizes to QuantKey GroupShape.
|
||||
|
||||
group_size: elements per group along K (col); 0 means full dimension.
|
||||
group_size_n: elements per group along N (row); 0 means 1 (per-row).
|
||||
|
||||
GroupShape convention: row = N dim, col = K dim.
|
||||
"""
|
||||
if group_size == 0 and group_size_n == 0:
|
||||
return GroupShape.PER_CHANNEL
|
||||
|
||||
row = group_size_n if group_size_n > 0 else 1
|
||||
col = group_size if group_size > 0 else -1
|
||||
return GroupShape(row=row, col=col)
|
||||
|
||||
|
||||
# ---- HummingWeightSchema (post-conversion) --------------------------------
|
||||
|
||||
|
||||
def _humming_weight_schema_to_quant_key(
|
||||
schema: "HummingWeightSchema",
|
||||
) -> QuantKey:
|
||||
from vllm.utils.humming import WeightScaleType
|
||||
|
||||
"""Convert a HummingWeightSchema to a QuantKey."""
|
||||
dtype = _HUMMING_TO_QUANT_DTYPE[schema.b_dtype]
|
||||
|
||||
if schema.bs_dtype is not None:
|
||||
scale_dtype = _HUMMING_TO_SCALE_DTYPE[schema.bs_dtype]
|
||||
else:
|
||||
scale_dtype = torch.float32
|
||||
|
||||
group_shape = _group_shape(
|
||||
schema.weight_scale_group_size,
|
||||
schema.weight_scale_group_size_n,
|
||||
)
|
||||
|
||||
scale = ScaleDesc(dtype=scale_dtype, static=True, group_shape=group_shape)
|
||||
|
||||
scale2 = None
|
||||
if schema.weight_scale_type == WeightScaleType.GROUP_TENSOR:
|
||||
scale2 = ScaleDesc(
|
||||
dtype=torch.float32,
|
||||
static=True,
|
||||
group_shape=GroupShape.PER_TENSOR,
|
||||
)
|
||||
|
||||
return QuantKey(
|
||||
dtype=dtype,
|
||||
scale=scale,
|
||||
scale2=scale2,
|
||||
symmetric=not schema.has_zero_point,
|
||||
)
|
||||
|
||||
|
||||
# ---- Checkpoint-format weight schemas (pre-conversion) --------------------
|
||||
|
||||
|
||||
def _fp8_weight_schema_to_quant_key(schema: "Fp8WeightSchema") -> QuantKey:
|
||||
if schema.weight_block_size is not None:
|
||||
gs_n, gs_k = schema.weight_block_size
|
||||
group_shape = GroupShape(row=gs_n, col=gs_k)
|
||||
else:
|
||||
group_shape = GroupShape.PER_CHANNEL
|
||||
|
||||
scale = ScaleDesc(dtype=torch.float32, static=True, group_shape=group_shape)
|
||||
return QuantKey(dtype=FP8_DTYPE, scale=scale, symmetric=True)
|
||||
|
||||
|
||||
def _awq_weight_schema_to_quant_key(schema: "AWQWeightSchema") -> QuantKey:
|
||||
group_shape = _group_shape(schema.group_size)
|
||||
scale = ScaleDesc(
|
||||
dtype=torch.float16,
|
||||
static=True,
|
||||
group_shape=group_shape,
|
||||
)
|
||||
return QuantKey(
|
||||
dtype=INT4_DTYPE,
|
||||
scale=scale,
|
||||
symmetric=not schema.zero_point,
|
||||
)
|
||||
|
||||
|
||||
def _gptq_weight_schema_to_quant_key(schema: "GPTQWeightSchema") -> QuantKey:
|
||||
group_shape = _group_shape(schema.group_size)
|
||||
scale = ScaleDesc(
|
||||
dtype=torch.float16,
|
||||
static=True,
|
||||
group_shape=group_shape,
|
||||
)
|
||||
return QuantKey(dtype=INT4_DTYPE, scale=scale, symmetric=schema.sym)
|
||||
|
||||
|
||||
def _compressed_tensors_weight_schema_to_quant_key(
|
||||
schema: "CompressedTensorsWeightSchema",
|
||||
) -> QuantKey:
|
||||
# Determine dtype from format/type/num_bits
|
||||
fmt = schema.format
|
||||
if fmt in ("int-quantized", "float-quantized", "naive-quantized"):
|
||||
dtype = INT8_DTYPE if schema.type == "int" else FP8_DTYPE
|
||||
elif "nvfp4" in fmt or "mxfp4" in fmt:
|
||||
dtype = FP4_DTYPE
|
||||
else:
|
||||
dtype = _HUMMING_TO_QUANT_DTYPE[
|
||||
humming_dtypes.DataType.from_str(f"uint{schema.num_bits}")
|
||||
]
|
||||
|
||||
# Determine group shape from strategy
|
||||
if schema.strategy in ("group", "tensor_group"):
|
||||
group_shape = _group_shape(schema.group_size or 0)
|
||||
elif schema.strategy == "block" and schema.block_structure is not None:
|
||||
group_shape = GroupShape(
|
||||
row=schema.block_structure[0],
|
||||
col=schema.block_structure[1],
|
||||
)
|
||||
else:
|
||||
group_shape = GroupShape.PER_CHANNEL
|
||||
|
||||
# Determine scale dtype
|
||||
if "mxfp" in fmt:
|
||||
scale_dtype = MXFP_SCALE_DTYPE
|
||||
elif "nvfp4" in fmt:
|
||||
scale_dtype = FP8_DTYPE
|
||||
else:
|
||||
scale_dtype = torch.float32
|
||||
|
||||
scale = ScaleDesc(dtype=scale_dtype, static=True, group_shape=group_shape)
|
||||
|
||||
scale2 = None
|
||||
if "nvfp4" in fmt or schema.strategy == "tensor_group":
|
||||
scale2 = ScaleDesc(
|
||||
dtype=torch.float32,
|
||||
static=True,
|
||||
group_shape=GroupShape.PER_TENSOR,
|
||||
)
|
||||
|
||||
return QuantKey(
|
||||
dtype=dtype,
|
||||
scale=scale,
|
||||
scale2=scale2,
|
||||
symmetric=schema.symmetric,
|
||||
)
|
||||
|
||||
|
||||
# ---- Dispatch for any BaseWeightSchema ------------------------------------
|
||||
|
||||
|
||||
def weight_schema_to_quant_key(
|
||||
schema: "BaseWeightSchema",
|
||||
) -> QuantKey:
|
||||
from vllm.utils.humming import (
|
||||
AWQWeightSchema,
|
||||
BitnetWeightSchema,
|
||||
CompressedTensorsWeightSchema,
|
||||
Fp8WeightSchema,
|
||||
GptOssMxfp4WeightSchema,
|
||||
GPTQWeightSchema,
|
||||
HummingWeightSchema,
|
||||
ModeloptMxfp8WeightSchema,
|
||||
ModeloptNvfp4WeightSchema,
|
||||
Mxfp4WeightSchema,
|
||||
)
|
||||
|
||||
"""Convert any BaseWeightSchema to a QuantKey."""
|
||||
if isinstance(schema, HummingWeightSchema):
|
||||
return _humming_weight_schema_to_quant_key(schema)
|
||||
|
||||
# Schemas with fixed QuantKeys
|
||||
if isinstance(schema, (Mxfp4WeightSchema, GptOssMxfp4WeightSchema)):
|
||||
return QuantKey(
|
||||
dtype=FP4_DTYPE,
|
||||
scale=ScaleDesc(MXFP_SCALE_DTYPE, True, GroupShape(1, 32)),
|
||||
)
|
||||
if isinstance(schema, ModeloptMxfp8WeightSchema):
|
||||
return QuantKey(
|
||||
dtype=FP8_DTYPE,
|
||||
scale=ScaleDesc(MXFP_SCALE_DTYPE, True, GroupShape(1, 32)),
|
||||
)
|
||||
if isinstance(schema, ModeloptNvfp4WeightSchema):
|
||||
return QuantKey(
|
||||
dtype=FP4_DTYPE,
|
||||
scale=ScaleDesc(FP8_DTYPE, True, GroupShape(1, 16)),
|
||||
scale2=ScaleDesc(torch.float32, True, GroupShape.PER_TENSOR),
|
||||
)
|
||||
if isinstance(schema, BitnetWeightSchema):
|
||||
return QuantKey(
|
||||
dtype=torch.uint8,
|
||||
scale=ScaleDesc(torch.float32, True, GroupShape.PER_CHANNEL),
|
||||
)
|
||||
|
||||
# Schemas requiring config inspection
|
||||
if isinstance(schema, Fp8WeightSchema):
|
||||
return _fp8_weight_schema_to_quant_key(schema)
|
||||
if isinstance(schema, AWQWeightSchema):
|
||||
return _awq_weight_schema_to_quant_key(schema)
|
||||
if isinstance(schema, GPTQWeightSchema):
|
||||
return _gptq_weight_schema_to_quant_key(schema)
|
||||
if isinstance(schema, CompressedTensorsWeightSchema):
|
||||
return _compressed_tensors_weight_schema_to_quant_key(schema)
|
||||
|
||||
raise TypeError(f"Unsupported weight schema type: {type(schema)}")
|
||||
|
||||
|
||||
# ---- HummingInputSchema (post-conversion) ----------------------------------
|
||||
|
||||
|
||||
def _humming_input_schema_to_quant_key(
|
||||
schema: "HummingInputSchema",
|
||||
) -> QuantKey | None:
|
||||
"""Convert a HummingInputSchema to a QuantKey. Returns None if
|
||||
the schema represents unquantized (bf16/fp16) inputs."""
|
||||
if schema.a_dtype is None or schema.a_dtype.num_bits >= 16:
|
||||
return None
|
||||
|
||||
dtype = _HUMMING_TO_QUANT_DTYPE[schema.a_dtype]
|
||||
|
||||
gs = schema.input_scale_group_size
|
||||
group_shape = GroupShape(row=1, col=gs) if gs > 0 else GroupShape.PER_TOKEN
|
||||
|
||||
# Pick the scale dtype the Humming kernel actually consumes. An explicit
|
||||
# input_scale_dtype always wins. Otherwise infer from the grouping: MX
|
||||
# microscale activations (group size 32, e.g. MXFP8) carry an e8m0 (uint8)
|
||||
# scale, while block-FP8 (group size 128) and per-token FP8/int8 carry a
|
||||
# float32 scale. Getting this right lets a grouped FP8 activation match
|
||||
# kFp8Dynamic128Sym instead of an unmatchable uint8-scaled key.
|
||||
if schema.input_scale_dtype is not None:
|
||||
scale_dtype = _HUMMING_TO_SCALE_DTYPE[schema.input_scale_dtype]
|
||||
elif gs == 32:
|
||||
scale_dtype = MXFP_SCALE_DTYPE
|
||||
else:
|
||||
scale_dtype = torch.float32
|
||||
|
||||
scale = ScaleDesc(dtype=scale_dtype, static=False, group_shape=group_shape)
|
||||
|
||||
return QuantKey(dtype=dtype, scale=scale, symmetric=True)
|
||||
|
||||
|
||||
# ---- Checkpoint-format input schemas (pre-conversion) ----------------------
|
||||
|
||||
|
||||
def _resolve_input_quant_key(
|
||||
origin_a_dtype: "humming_dtypes.DataType",
|
||||
group_size: int,
|
||||
) -> QuantKey | None:
|
||||
from vllm.utils.humming import HummingInputSchema
|
||||
|
||||
"""Resolve the actual activation QuantKey after platform fallback."""
|
||||
a_dtype = HummingInputSchema().get_fallback_input_dtype(origin_a_dtype)
|
||||
if a_dtype is None or a_dtype.num_bits >= 16:
|
||||
return None
|
||||
|
||||
dtype = _HUMMING_TO_QUANT_DTYPE[a_dtype]
|
||||
gs = group_size if a_dtype == humming_dtypes.float4e2m1 else 0
|
||||
group_shape = GroupShape(row=1, col=gs) if gs > 0 else GroupShape.PER_TOKEN
|
||||
scale_dtype = MXFP_SCALE_DTYPE if gs > 0 else torch.float32
|
||||
|
||||
scale = ScaleDesc(dtype=scale_dtype, static=False, group_shape=group_shape)
|
||||
return QuantKey(dtype=dtype, scale=scale, symmetric=True)
|
||||
|
||||
|
||||
def _compressed_tensors_input_schema_to_quant_key(
|
||||
schema: "CompressedTensorsInputSchema",
|
||||
) -> QuantKey | None:
|
||||
type_bits_to_dtype = {
|
||||
("float", 8): humming_dtypes.float8e4m3,
|
||||
("float", 4): humming_dtypes.float4e2m1,
|
||||
("int", 8): humming_dtypes.int8,
|
||||
("int", 4): humming_dtypes.int4,
|
||||
}
|
||||
origin = type_bits_to_dtype.get((schema.type, schema.num_bits))
|
||||
if origin is None:
|
||||
return None
|
||||
return _resolve_input_quant_key(origin, schema.group_size)
|
||||
|
||||
|
||||
# ---- Dispatch for any BaseInputSchema -------------------------------------
|
||||
|
||||
|
||||
def input_schema_to_quant_key(
|
||||
schema: "BaseInputSchema",
|
||||
) -> QuantKey | None:
|
||||
from vllm.utils.humming import (
|
||||
CompressedTensorsInputSchema,
|
||||
Fp8InputSchema,
|
||||
HummingInputSchema,
|
||||
ModeloptNvfp4InputSchema,
|
||||
)
|
||||
|
||||
"""Convert any BaseInputSchema to a QuantKey. Returns None if
|
||||
the schema represents unquantized (bf16/fp16) inputs."""
|
||||
if isinstance(schema, HummingInputSchema):
|
||||
return _humming_input_schema_to_quant_key(schema)
|
||||
|
||||
if isinstance(schema, Fp8InputSchema):
|
||||
return _resolve_input_quant_key(humming_dtypes.float8e4m3, 0)
|
||||
|
||||
if isinstance(schema, ModeloptNvfp4InputSchema):
|
||||
return _resolve_input_quant_key(
|
||||
humming_dtypes.float8e4m3,
|
||||
schema.group_size,
|
||||
)
|
||||
|
||||
if isinstance(schema, CompressedTensorsInputSchema):
|
||||
return _compressed_tensors_input_schema_to_quant_key(schema)
|
||||
|
||||
raise TypeError(f"Unsupported input schema type: {type(schema)}")
|
||||
|
||||
|
||||
def humming_is_layer_skipped(config: dict[str, Any], prefix: str):
|
||||
if not config:
|
||||
return True
|
||||
|
||||
keys = ["ignored_layers", "ignore", "modules_to_not_convert"]
|
||||
ignored_layers: list[str] = []
|
||||
for key in keys:
|
||||
candidate = config.get(key, []) or []
|
||||
if candidate:
|
||||
ignored_layers = candidate
|
||||
break
|
||||
|
||||
if any(module_name in prefix for module_name in ignored_layers):
|
||||
return True
|
||||
if "lm_head" in prefix:
|
||||
return True
|
||||
|
||||
for regex in config.get("dynamic", {}):
|
||||
if regex[:1] != "-":
|
||||
continue
|
||||
if re.match(regex[2:], prefix):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def convert_linear_layer_to_humming_standard(
|
||||
layer: LinearBase, name_map: dict[str, str]
|
||||
):
|
||||
"""Rename/reshape a linear layer's quantized params (the canonical MPLinear
|
||||
layout: ``weight_packed`` int32 + ``weight_scale``) into the parameter names
|
||||
and layout humming's weight schema expects (``weight`` / ``weight_scale``)."""
|
||||
for name, checkpoint_name in name_map.items():
|
||||
tensor = getattr(layer, checkpoint_name)
|
||||
delattr(layer, checkpoint_name)
|
||||
|
||||
if name == "weight":
|
||||
input_dim = getattr(tensor, "input_dim", 1)
|
||||
output_dim = getattr(tensor, "output_dim", 0)
|
||||
|
||||
if input_dim == 0 and output_dim == 1:
|
||||
tensor = tensor.transpose(1, 0).contiguous()
|
||||
else:
|
||||
assert output_dim == 0 and input_dim == 1
|
||||
|
||||
tensor = tensor.view(tensor.size(0), -1).view(torch.int32)
|
||||
elif name in ["weight_scale", "zero_point"]:
|
||||
if getattr(tensor, "output_dim", 0) == 1:
|
||||
tensor = tensor.transpose(0, 1).contiguous()
|
||||
if tensor.ndim == 1:
|
||||
tensor = tensor.unsqueeze(1)
|
||||
|
||||
tensor = tensor.view(torch.int32) if name == "zero_point" else tensor
|
||||
|
||||
if isinstance(tensor, torch.nn.Parameter):
|
||||
param = tensor
|
||||
else:
|
||||
param = torch.nn.Parameter(tensor, requires_grad=False)
|
||||
|
||||
setattr(layer, name, param)
|
||||
|
||||
|
||||
def prepare_humming_linear_layer_config(
|
||||
layer: LinearBase,
|
||||
quant_config: dict,
|
||||
input_quant_config: dict | None = None,
|
||||
) -> "LayerConfig":
|
||||
from vllm.utils.humming import (
|
||||
BaseInputSchema,
|
||||
BaseWeightSchema,
|
||||
HummingInputSchema,
|
||||
prepare_layer_config,
|
||||
transform_humming_tensors,
|
||||
)
|
||||
|
||||
weight_schema = BaseWeightSchema.from_config(quant_config)
|
||||
if input_quant_config is not None:
|
||||
input_schema = BaseInputSchema.from_config(input_quant_config)
|
||||
else:
|
||||
input_schema = HummingInputSchema()
|
||||
|
||||
# ReplicatedLinear has no TP partitioning and so does not set
|
||||
# input_size_per_partition; for it that is just input_size. Use hasattr
|
||||
# rather than getattr's default arg, which is evaluated eagerly and would
|
||||
# raise on layers lacking input_size (e.g. ParallelLMHead).
|
||||
if hasattr(layer, "input_size_per_partition"):
|
||||
input_size_per_partition = layer.input_size_per_partition
|
||||
else:
|
||||
input_size_per_partition = layer.input_size
|
||||
shape_k_stacks = [input_size_per_partition]
|
||||
shape_n_stacks = layer.output_partition_sizes
|
||||
|
||||
# Step 1: convert weight and input schemas to humming standard format
|
||||
weight_schema, tensors = weight_schema.convert_humming(
|
||||
tensors=dict(layer.named_parameters()),
|
||||
shape_n_stacks=shape_n_stacks,
|
||||
shape_k_stacks=shape_k_stacks,
|
||||
param_dtype=layer.params_dtype,
|
||||
)
|
||||
input_schema, _ = input_schema.convert_humming(
|
||||
tensors={},
|
||||
shape_n_stacks=shape_n_stacks,
|
||||
shape_k_stacks=shape_k_stacks,
|
||||
param_dtype=layer.params_dtype,
|
||||
)
|
||||
|
||||
layer.weight_schema = weight_schema
|
||||
|
||||
# Step 2: transform weight (humming standard format) for forwarding.
|
||||
config = prepare_layer_config(
|
||||
shape_n=sum(layer.output_partition_sizes),
|
||||
shape_k=input_size_per_partition,
|
||||
weight_schema=weight_schema,
|
||||
input_schema=input_schema,
|
||||
pad_n_to_multiple=256,
|
||||
pad_k_to_multiple=128,
|
||||
has_bias=layer.has_bias,
|
||||
torch_dtype=layer.params_dtype,
|
||||
)
|
||||
tensors = transform_humming_tensors(config, tensors)
|
||||
for name, _ in list(layer.named_parameters()):
|
||||
delattr(layer, name)
|
||||
for name, tensor in tensors.items():
|
||||
param = torch.nn.Parameter(tensor, requires_grad=False)
|
||||
setattr(layer, name, param)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def get_humming_linear_compute_config() -> str:
|
||||
return json.dumps(
|
||||
{
|
||||
"use_batch_invariant": envs.VLLM_BATCH_INVARIANT,
|
||||
"use_f16_accum": envs.VLLM_HUMMING_USE_F16_ACCUM,
|
||||
"gemm_type": "dense",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def apply_humming_linear(
|
||||
layer: LinearBase,
|
||||
x: torch.Tensor,
|
||||
*,
|
||||
layer_config: "LayerConfig",
|
||||
compute_config: str,
|
||||
locks: torch.Tensor,
|
||||
) -> torch.Tensor:
|
||||
from vllm.utils.humming import humming_forward
|
||||
|
||||
flatten_inputs = x.reshape(-1, x.size(-1))
|
||||
output = humming_forward(
|
||||
layer_config,
|
||||
inputs=flatten_inputs,
|
||||
weight=layer.weight,
|
||||
weight_scale=getattr(layer, "weight_scale", None),
|
||||
zero_point=getattr(layer, "zero_point", None),
|
||||
bias=getattr(layer, "bias", None),
|
||||
weight_scale_2=getattr(layer, "weight_scale_2", None),
|
||||
locks=locks,
|
||||
compute_config=compute_config,
|
||||
)
|
||||
return output.view(*x.shape[:-1], output.size(-1))
|
||||
w1_hadamard_block_size: int = 1
|
||||
w2_hadamard_block_size: int = 1
|
||||
w1_input_scale: torch.Tensor | None = None
|
||||
w2_input_scale: torch.Tensor | None = None
|
||||
w1_input_scale_2: torch.Tensor | None = None
|
||||
w2_input_scale_2: torch.Tensor | None = None
|
||||
|
||||
|
||||
def make_humming_moe_quant_config(
|
||||
@@ -648,8 +142,12 @@ def get_humming_moe_quant_config(
|
||||
q_dtype is not None
|
||||
and q_dtype.startswith("float8")
|
||||
and input_scale_group_size == 128
|
||||
and weight_schema.hadamard_block_size <= 1
|
||||
and input_schema.input_quant_mode in (None, "dynamic_group")
|
||||
):
|
||||
q_dtype = _HUMMING_TO_QUANT_DTYPE.get(input_schema.a_dtype, FP8_DTYPE)
|
||||
q_dtype = humming_schema._HUMMING_TO_QUANT_DTYPE.get(
|
||||
input_schema.a_dtype, FP8_DTYPE
|
||||
)
|
||||
activation_group_shape = GroupShape(row=1, col=input_scale_group_size)
|
||||
|
||||
weight_scale_group_size = weight_schema.weight_scale_group_size
|
||||
@@ -665,7 +163,7 @@ def get_humming_moe_quant_config(
|
||||
else:
|
||||
weight_group_shape = GroupShape(row=weight_scale_group_size, col=1)
|
||||
|
||||
return make_humming_moe_quant_config(
|
||||
config = make_humming_moe_quant_config(
|
||||
quant_dtype=q_dtype,
|
||||
weight_dtype=str(weight_schema.b_dtype),
|
||||
weight_group_shape=weight_group_shape,
|
||||
@@ -683,6 +181,17 @@ def get_humming_moe_quant_config(
|
||||
gemm1_clamp_limit=gemm1_clamp_limit,
|
||||
humming_configs=humming_configs,
|
||||
)
|
||||
for prefix, sublayer in (("w1", "w13"), ("w2", "w2")):
|
||||
setattr(
|
||||
config,
|
||||
f"{prefix}_hadamard_block_size",
|
||||
layer.weight_schemas[sublayer].hadamard_block_size,
|
||||
)
|
||||
for name in ("input_scale", "input_scale_2"):
|
||||
key = f"{prefix}_{name}"
|
||||
value = getattr(layer, f"{sublayer}_{name}", None)
|
||||
setattr(config, key, value)
|
||||
return config
|
||||
|
||||
|
||||
def select_humming_moe_experts(
|
||||
@@ -846,12 +355,6 @@ def _convert_sublayer_to_humming(
|
||||
Tuple of (converted_weight_schema, converted_input_schema)
|
||||
|
||||
"""
|
||||
from vllm.utils.humming import HummingWeightSchema
|
||||
|
||||
if isinstance(weight_schema, HummingWeightSchema):
|
||||
# Already in Humming format
|
||||
return weight_schema, input_schema
|
||||
|
||||
tensors = _extract_sublayer_tensors(layer, sublayer_name)
|
||||
|
||||
shape_k_stacks = [shape_k]
|
||||
@@ -867,13 +370,14 @@ def _convert_sublayer_to_humming(
|
||||
num_experts=num_experts,
|
||||
)
|
||||
|
||||
converted_input_schema, _ = input_schema.convert_humming(
|
||||
tensors=converted_tensors,
|
||||
converted_input_schema, input_tensors = input_schema.convert_humming(
|
||||
tensors=tensors,
|
||||
shape_n_stacks=shape_n_stacks,
|
||||
shape_k_stacks=shape_k_stacks,
|
||||
param_dtype=param_dtype,
|
||||
num_experts=num_experts,
|
||||
)
|
||||
converted_tensors.update(input_tensors)
|
||||
|
||||
_replace_layer_parameters(layer, sublayer_name, converted_tensors)
|
||||
|
||||
@@ -907,11 +411,16 @@ def _prepare_and_transform_sublayer(
|
||||
has_bias=has_bias,
|
||||
num_experts=num_experts,
|
||||
torch_dtype=param_dtype,
|
||||
device=getattr(layer, f"{sublayer_name}_weight").device,
|
||||
)
|
||||
tensors = transform_humming_tensors(
|
||||
config,
|
||||
_extract_sublayer_tensors(layer, sublayer_name),
|
||||
)
|
||||
source_tensors = _extract_sublayer_tensors(layer, sublayer_name)
|
||||
for name in ("input_scale", "input_scale_2"):
|
||||
if name != input_schema.static_tensor_scale_name:
|
||||
source_tensors.pop(name, None)
|
||||
tensors = transform_humming_tensors(config, source_tensors)
|
||||
for name in ("input_scale", "input_scale_2"):
|
||||
if name in source_tensors:
|
||||
tensors[name] = source_tensors[name]
|
||||
_replace_layer_parameters(layer, sublayer_name, tensors)
|
||||
return config
|
||||
|
||||
@@ -927,6 +436,7 @@ def _process_single_sublayer(
|
||||
num_experts: int,
|
||||
param_dtype: torch.dtype,
|
||||
force_weight_schema: Any | None = None,
|
||||
allow_input_schema_fallback: bool = True,
|
||||
) -> tuple[Any, Any, "LayerConfig"]:
|
||||
"""Process a single sublayer: convert, optionally requant, prepare, and transform.
|
||||
|
||||
@@ -944,6 +454,7 @@ def _process_single_sublayer(
|
||||
num_experts: Number of experts
|
||||
param_dtype: Parameter data type
|
||||
force_weight_schema: Optional schema to force requantization to
|
||||
allow_input_schema_fallback: Whether incompatible input schemas may be replaced.
|
||||
|
||||
Returns:
|
||||
Tuple of the final weight schema, input schema, and Humming layer config.
|
||||
@@ -966,19 +477,28 @@ def _process_single_sublayer(
|
||||
# Step 2: Force requant if needed
|
||||
assert isinstance(current_weight_schema, HummingWeightSchema)
|
||||
if force_weight_schema is not None and current_weight_schema != force_weight_schema:
|
||||
tensors = _extract_sublayer_tensors(layer, sublayer_name)
|
||||
source_tensors = _extract_sublayer_tensors(layer, sublayer_name)
|
||||
|
||||
tensors = current_weight_schema.requant_tensors(
|
||||
tensors=tensors,
|
||||
tensors=source_tensors,
|
||||
target_weight_schema=force_weight_schema,
|
||||
param_dtype=param_dtype,
|
||||
)
|
||||
name = current_input_schema.static_tensor_scale_name
|
||||
if name is not None:
|
||||
tensors[name] = source_tensors[name]
|
||||
|
||||
current_weight_schema = force_weight_schema
|
||||
_replace_layer_parameters(layer, sublayer_name, tensors, preserve_bias=True)
|
||||
del tensors
|
||||
|
||||
# Step 3: Prepare layer metadata and transform weights
|
||||
current_input_schema = humming_schema.check_and_fallback_input_schema(
|
||||
weight_schema=current_weight_schema,
|
||||
input_schema=current_input_schema,
|
||||
param_dtype=param_dtype,
|
||||
allow_fallback=allow_input_schema_fallback,
|
||||
)
|
||||
config = _prepare_and_transform_sublayer(
|
||||
layer=layer,
|
||||
sublayer_name=sublayer_name,
|
||||
@@ -1001,6 +521,7 @@ def convert_to_humming_moe_kernel_format(
|
||||
weight_schema: Any | None = None,
|
||||
input_schema: Any | None = None,
|
||||
force_weight_schema: Any | None = None,
|
||||
allow_input_schema_fallback: bool = True,
|
||||
) -> dict[str, "LayerConfig"]:
|
||||
"""Convert MoE weights from checkpoint format to Humming kernel format.
|
||||
|
||||
@@ -1023,6 +544,7 @@ def convert_to_humming_moe_kernel_format(
|
||||
input_schema: Optional initial input quantization schema.
|
||||
If None, built from quant_config or env vars.
|
||||
force_weight_schema: Optional schema to force requantization to
|
||||
allow_input_schema_fallback: Whether incompatible input schemas may be replaced.
|
||||
|
||||
Side effects:
|
||||
- Modifies layer parameters in place
|
||||
@@ -1041,20 +563,23 @@ def convert_to_humming_moe_kernel_format(
|
||||
"Must provide either weight_schema/input_schema or quant_config"
|
||||
)
|
||||
|
||||
from vllm.model_executor.layers.quantization.utils.humming_utils import (
|
||||
humming_is_layer_skipped,
|
||||
)
|
||||
from vllm.utils.humming import BaseWeightSchema, HummingInputSchema
|
||||
|
||||
if weight_schema is None:
|
||||
weight_schema = BaseWeightSchema.from_config(quant_config)
|
||||
|
||||
if input_schema is None:
|
||||
input_quant_config = envs.VLLM_HUMMING_INPUT_QUANT_CONFIG or {}
|
||||
input_quant_config = (envs.VLLM_HUMMING_INPUT_QUANT_CONFIG or {}).copy()
|
||||
if humming_is_layer_skipped(input_quant_config, layer.layer_name):
|
||||
input_schema = HummingInputSchema()
|
||||
else:
|
||||
# TODO: read input_quant_config from quant_config
|
||||
input_quant_config = humming_schema.resolve_humming_layer_config(
|
||||
input_quant_config, layer.layer_name
|
||||
)
|
||||
allow_input_schema_fallback = input_quant_config.pop(
|
||||
"allow_fallback", False
|
||||
)
|
||||
input_schema = HummingInputSchema.from_config(input_quant_config)
|
||||
|
||||
# Build sublayer configs from layer properties if not provided
|
||||
@@ -1089,6 +614,7 @@ def convert_to_humming_moe_kernel_format(
|
||||
num_experts=num_experts,
|
||||
param_dtype=param_dtype,
|
||||
force_weight_schema=force_weight_schema,
|
||||
allow_input_schema_fallback=allow_input_schema_fallback,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,437 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Map Humming schemas and handle shared checkpoint quantization settings."""
|
||||
|
||||
import dataclasses
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import regex as re
|
||||
import torch
|
||||
|
||||
from vllm.logger import init_logger
|
||||
from vllm.model_executor.layers.quantization.utils.quant_utils import (
|
||||
FP4_DTYPE,
|
||||
FP8_DTYPE,
|
||||
INT4_DTYPE,
|
||||
INT8_DTYPE,
|
||||
MXFP_SCALE_DTYPE,
|
||||
GroupShape,
|
||||
QuantKey,
|
||||
ScaleDesc,
|
||||
)
|
||||
from vllm.platforms import current_platform
|
||||
from vllm.scalar_type import scalar_types
|
||||
from vllm.utils.import_utils import has_humming
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from vllm.utils.humming import (
|
||||
BaseInputSchema,
|
||||
BaseWeightSchema,
|
||||
HummingInputSchema,
|
||||
HummingWeightSchema,
|
||||
)
|
||||
from vllm.utils.humming import dtypes as humming_dtypes
|
||||
|
||||
|
||||
def humming_update_schema_hadamard_block_size(
|
||||
weight_schema: "HummingWeightSchema",
|
||||
input_schema: "HummingInputSchema",
|
||||
shape_k: int,
|
||||
) -> "HummingWeightSchema":
|
||||
assert shape_k > 0
|
||||
block_size = 32
|
||||
weight_group_size = weight_schema.weight_scale_group_size
|
||||
input_group_size = input_schema.input_scale_group_size
|
||||
for group_size in (weight_group_size, input_group_size):
|
||||
if group_size is not None and group_size > 0:
|
||||
block_size = min(block_size, group_size)
|
||||
block_size = 1 << (block_size.bit_length() - 1)
|
||||
while shape_k % block_size:
|
||||
block_size //= 2
|
||||
return dataclasses.replace(weight_schema, hadamard_block_size=block_size)
|
||||
|
||||
|
||||
if has_humming():
|
||||
from vllm.utils.humming import dtypes as humming_dtypes
|
||||
|
||||
_HUMMING_TO_QUANT_DTYPE: dict[humming_dtypes.DataType, Any] = {
|
||||
humming_dtypes.float4e0m3: FP4_DTYPE,
|
||||
humming_dtypes.float4e2m1: FP4_DTYPE,
|
||||
humming_dtypes.float6e2m3: scalar_types.float6_e2m3f,
|
||||
humming_dtypes.float6e3m2: scalar_types.float6_e3m2f,
|
||||
humming_dtypes.float8e3m4: FP8_DTYPE,
|
||||
humming_dtypes.float8e4m3: FP8_DTYPE,
|
||||
humming_dtypes.float8e5m2: torch.float8_e5m2,
|
||||
humming_dtypes.int8: torch.int8,
|
||||
humming_dtypes.uint4: INT4_DTYPE,
|
||||
humming_dtypes.uint8: INT8_DTYPE,
|
||||
humming_dtypes.uint2: torch.uint8,
|
||||
humming_dtypes.uint3: torch.uint8,
|
||||
humming_dtypes.uint5: torch.uint8,
|
||||
humming_dtypes.uint6: torch.uint8,
|
||||
humming_dtypes.uint7: torch.uint8,
|
||||
}
|
||||
|
||||
_HUMMING_TO_SCALE_DTYPE: dict[humming_dtypes.DataType, torch.dtype] = {
|
||||
humming_dtypes.float8e8m0: MXFP_SCALE_DTYPE,
|
||||
humming_dtypes.float8e4m3: FP8_DTYPE,
|
||||
humming_dtypes.float16: torch.float16,
|
||||
humming_dtypes.bfloat16: torch.bfloat16,
|
||||
humming_dtypes.float32: torch.float32,
|
||||
}
|
||||
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
|
||||
def _group_shape(group_size: int, group_size_n: int = 0) -> GroupShape:
|
||||
"""Map humming group sizes to QuantKey GroupShape.
|
||||
|
||||
group_size: elements per group along K (col); 0 means full dimension.
|
||||
group_size_n: elements per group along N (row); 0 means 1 (per-row).
|
||||
|
||||
GroupShape convention: row = N dim, col = K dim.
|
||||
"""
|
||||
if group_size == 0 and group_size_n == 0:
|
||||
return GroupShape.PER_CHANNEL
|
||||
|
||||
row = group_size_n if group_size_n > 0 else 1
|
||||
col = group_size if group_size > 0 else -1
|
||||
return GroupShape(row=row, col=col)
|
||||
|
||||
|
||||
def weight_schema_to_quant_key(
|
||||
schema: "BaseWeightSchema",
|
||||
param_dtype: torch.dtype | None = None,
|
||||
) -> QuantKey:
|
||||
from vllm.utils.humming import WeightScale2Type, WeightScaleType
|
||||
|
||||
if param_dtype is None:
|
||||
param_dtype = torch.get_default_dtype()
|
||||
if param_dtype not in [torch.float16, torch.bfloat16]:
|
||||
param_dtype = torch.bfloat16
|
||||
if not current_platform.has_device_capability(80):
|
||||
param_dtype = torch.float16
|
||||
|
||||
schema = schema.to_humming_schema(param_dtype)
|
||||
dtype = _HUMMING_TO_QUANT_DTYPE[schema.b_dtype]
|
||||
|
||||
if schema.bs_dtype is not None:
|
||||
scale_dtype = _HUMMING_TO_SCALE_DTYPE[schema.bs_dtype]
|
||||
else:
|
||||
scale_dtype = torch.float32
|
||||
|
||||
group_shape = _group_shape(
|
||||
schema.weight_scale_group_size,
|
||||
schema.weight_scale_group_size_n,
|
||||
)
|
||||
if schema.weight_scale_type == WeightScaleType.TENSOR:
|
||||
group_shape = GroupShape.PER_TENSOR
|
||||
|
||||
scale = ScaleDesc(dtype=scale_dtype, static=True, group_shape=group_shape)
|
||||
|
||||
scale2 = None
|
||||
if schema.weight_scale_2_type == WeightScale2Type.TENSOR:
|
||||
scale2 = ScaleDesc(
|
||||
dtype=torch.float32,
|
||||
static=True,
|
||||
group_shape=GroupShape.PER_TENSOR,
|
||||
)
|
||||
|
||||
return QuantKey(
|
||||
dtype=dtype,
|
||||
scale=scale,
|
||||
scale2=scale2,
|
||||
symmetric=not schema.has_zero_point,
|
||||
)
|
||||
|
||||
|
||||
def input_schema_to_quant_key(
|
||||
schema: "BaseInputSchema",
|
||||
param_dtype: torch.dtype | None = None,
|
||||
) -> QuantKey | None:
|
||||
from vllm.utils.humming import InputQuantizationMode
|
||||
|
||||
if param_dtype is None:
|
||||
param_dtype = torch.get_default_dtype()
|
||||
if param_dtype not in [torch.float16, torch.bfloat16]:
|
||||
param_dtype = torch.bfloat16
|
||||
if not current_platform.has_device_capability(80):
|
||||
param_dtype = torch.float16
|
||||
|
||||
schema = schema.to_humming_schema(param_dtype)
|
||||
if schema.a_dtype is None or schema.a_dtype.num_bits >= 16:
|
||||
return None
|
||||
|
||||
mode = schema.input_quant_mode
|
||||
if mode == InputQuantizationMode.Disabled:
|
||||
return None
|
||||
dtype = _HUMMING_TO_QUANT_DTYPE[schema.a_dtype]
|
||||
|
||||
gs = schema.input_scale_group_size
|
||||
group_shape = GroupShape(row=1, col=gs) if gs > 0 else GroupShape.PER_TOKEN
|
||||
|
||||
# Pick the scale dtype the Humming kernel actually consumes. An explicit
|
||||
# input_scale_dtype always wins. Otherwise infer from the grouping: MX
|
||||
# microscale activations (group size 32, e.g. MXFP8) carry an e8m0 (uint8)
|
||||
# scale, while block-FP8 (group size 128) and per-token FP8/int8 carry a
|
||||
# float32 scale. Getting this right lets a grouped FP8 activation match
|
||||
# kFp8Dynamic128Sym instead of an unmatchable uint8-scaled key.
|
||||
if schema.input_scale_dtype is not None:
|
||||
scale_dtype = _HUMMING_TO_SCALE_DTYPE[schema.input_scale_dtype]
|
||||
elif gs == 16:
|
||||
scale_dtype = FP8_DTYPE
|
||||
elif gs == 32:
|
||||
scale_dtype = MXFP_SCALE_DTYPE
|
||||
else:
|
||||
scale_dtype = torch.float32
|
||||
|
||||
if mode == InputQuantizationMode.StaticTensor:
|
||||
group_shape = GroupShape.PER_TENSOR
|
||||
scale = ScaleDesc(
|
||||
dtype=scale_dtype,
|
||||
static=mode == InputQuantizationMode.StaticTensor,
|
||||
group_shape=group_shape,
|
||||
)
|
||||
scale2 = None
|
||||
has_scale2 = (
|
||||
InputQuantizationMode.StaticTensorDynamicGroup,
|
||||
InputQuantizationMode.DynamicGroupToken,
|
||||
)
|
||||
if mode in has_scale2:
|
||||
static = mode == InputQuantizationMode.StaticTensorDynamicGroup
|
||||
scale2 = ScaleDesc(
|
||||
torch.float32,
|
||||
static,
|
||||
GroupShape.PER_TENSOR if static else GroupShape.PER_TOKEN,
|
||||
)
|
||||
|
||||
return QuantKey(dtype=dtype, scale=scale, scale2=scale2, symmetric=True)
|
||||
|
||||
|
||||
def quant_key_to_input_schema(key: QuantKey | None) -> "HummingInputSchema":
|
||||
from vllm.utils.humming import HummingInputSchema, InputQuantizationMode
|
||||
|
||||
if key is None:
|
||||
return HummingInputSchema(input_quant_mode=InputQuantizationMode.Disabled)
|
||||
|
||||
if not key.symmetric:
|
||||
raise ValueError("Humming input quantization must be symmetric")
|
||||
|
||||
quant_dtypes = {value: dtype for dtype, value in _HUMMING_TO_QUANT_DTYPE.items()}
|
||||
quant_dtypes[FP4_DTYPE] = humming_dtypes.float4e2m1
|
||||
quant_dtypes[FP8_DTYPE] = humming_dtypes.float8e4m3
|
||||
scale_dtypes = {value: dtype for dtype, value in _HUMMING_TO_SCALE_DTYPE.items()}
|
||||
if key.dtype not in quant_dtypes or key.scale.dtype not in scale_dtypes:
|
||||
raise ValueError(f"Unsupported Humming input or scale dtype: {key}")
|
||||
|
||||
scale = key.scale
|
||||
group_size = 0
|
||||
if scale.static and scale.group_shape.is_per_tensor() and key.scale2 is None:
|
||||
mode = InputQuantizationMode.StaticTensor
|
||||
elif not scale.static and scale.group_shape.is_per_token() and key.scale2 is None:
|
||||
mode = InputQuantizationMode.DynamicToken
|
||||
elif not scale.static and scale.group_shape.is_per_group():
|
||||
group_size = scale.group_shape.col
|
||||
scale2 = key.scale2
|
||||
if scale2 is None:
|
||||
mode = InputQuantizationMode.DynamicGroup
|
||||
elif scale2.dtype != torch.float32:
|
||||
raise ValueError("Humming secondary input scales must be float32")
|
||||
elif scale2.static and scale2.group_shape.is_per_tensor():
|
||||
mode = InputQuantizationMode.StaticTensorDynamicGroup
|
||||
elif not scale2.static and scale2.group_shape.is_per_token():
|
||||
mode = InputQuantizationMode.DynamicGroupToken
|
||||
else:
|
||||
raise ValueError(f"Unsupported Humming secondary input scale: {scale2}")
|
||||
else:
|
||||
raise ValueError(f"Unsupported Humming input scale: {key}")
|
||||
|
||||
if group_size == 0 and scale.dtype != torch.float32:
|
||||
raise ValueError("Humming tensor and token input scales must be float32")
|
||||
|
||||
return HummingInputSchema(
|
||||
a_dtype=quant_dtypes[key.dtype],
|
||||
input_scale_group_size=group_size,
|
||||
input_scale_dtype=scale_dtypes[scale.dtype],
|
||||
input_quant_mode=mode,
|
||||
)
|
||||
|
||||
|
||||
def check_and_fallback_input_schema(
|
||||
weight_schema: "BaseWeightSchema",
|
||||
input_schema: "BaseInputSchema",
|
||||
param_dtype: torch.dtype | None = None,
|
||||
allow_fallback: bool = True,
|
||||
) -> "HummingInputSchema":
|
||||
from vllm.utils.humming import HummingInputSchema, InputQuantizationMode
|
||||
|
||||
capability = current_platform.get_device_capability()
|
||||
assert capability is not None
|
||||
sm_version = capability.to_int()
|
||||
if param_dtype is None:
|
||||
param_dtype = torch.get_default_dtype()
|
||||
if param_dtype not in (torch.float16, torch.bfloat16):
|
||||
param_dtype = torch.bfloat16 if sm_version >= 80 else torch.float16
|
||||
assert param_dtype in (torch.float16, torch.bfloat16)
|
||||
|
||||
dtype_fallback_order_map = {
|
||||
humming_dtypes.float8e5m2: (
|
||||
humming_dtypes.float8e4m3,
|
||||
humming_dtypes.float8e3m4,
|
||||
humming_dtypes.int8,
|
||||
),
|
||||
humming_dtypes.float8e4m3: (
|
||||
humming_dtypes.float8e3m4,
|
||||
humming_dtypes.int8,
|
||||
),
|
||||
humming_dtypes.float8e3m4: (
|
||||
humming_dtypes.float8e4m3,
|
||||
humming_dtypes.int8,
|
||||
),
|
||||
humming_dtypes.float4e2m1: (
|
||||
humming_dtypes.float8e4m3,
|
||||
humming_dtypes.float8e3m4,
|
||||
humming_dtypes.int8,
|
||||
),
|
||||
humming_dtypes.float4e0m3: (
|
||||
humming_dtypes.int4,
|
||||
humming_dtypes.float8e4m3,
|
||||
humming_dtypes.float8e3m4,
|
||||
humming_dtypes.int8,
|
||||
),
|
||||
humming_dtypes.int8: (
|
||||
humming_dtypes.float8e4m3,
|
||||
humming_dtypes.float8e3m4,
|
||||
),
|
||||
humming_dtypes.int4: (
|
||||
humming_dtypes.int8,
|
||||
humming_dtypes.float8e4m3,
|
||||
humming_dtypes.float8e3m4,
|
||||
),
|
||||
}
|
||||
|
||||
weight_schema = weight_schema.to_humming_schema(param_dtype)
|
||||
input_schema = input_schema.to_humming_schema(param_dtype)
|
||||
fp6_dtypes = (humming_dtypes.float6e2m3, humming_dtypes.float6e3m2)
|
||||
if input_schema.a_dtype in fp6_dtypes:
|
||||
input_schema.a_dtype = humming_dtypes.float8e4m3
|
||||
a_dtype = input_schema.a_dtype
|
||||
input_bits = a_dtype.num_bits if a_dtype is not None else 16
|
||||
weight_group_size = weight_schema.weight_scale_group_size
|
||||
input_group_size = input_schema.input_scale_group_size
|
||||
input_scale_dtype = input_schema.input_scale_dtype
|
||||
|
||||
def is_deprecated(dtype: "humming_dtypes.DataType | None") -> bool:
|
||||
if dtype is None or dtype.num_bits == 16:
|
||||
return False
|
||||
is_int4_deprecated = dtype == humming_dtypes.int4 and sm_version >= 90
|
||||
is_int8_deprecated = dtype == humming_dtypes.int8 and 103 <= sm_version < 110
|
||||
# Humming has not yet implemented high-performance FP8 computation
|
||||
# on SM10X and SM11X. Therefore, it is temporarily considered deprecated.
|
||||
# TODO: removed this after Humming has high-performance FP8 computation
|
||||
is_fp8_deprecated = dtype.num_bits < 16 and 100 <= sm_version < 120
|
||||
return is_int4_deprecated or is_int8_deprecated or is_fp8_deprecated
|
||||
|
||||
if input_schema.is_compatible_with(weight_schema, param_dtype):
|
||||
if not allow_fallback:
|
||||
if is_deprecated(a_dtype):
|
||||
msg = f"Humming {a_dtype} activation is slow on SM{sm_version} now."
|
||||
logger.warning_once(msg)
|
||||
return input_schema
|
||||
|
||||
if a_dtype is None or a_dtype.num_bits == 16:
|
||||
return input_schema
|
||||
|
||||
if not is_deprecated(a_dtype):
|
||||
is_mxfp8 = (
|
||||
a_dtype.is_floating_point_type
|
||||
and a_dtype.num_bits == 8
|
||||
and input_group_size == 32
|
||||
and input_scale_dtype == humming_dtypes.float8e8m0
|
||||
and sm_version >= 120
|
||||
)
|
||||
is_groupwise = input_group_size > 0 or weight_group_size > 0
|
||||
if input_bits == 8 and is_groupwise and not is_mxfp8:
|
||||
# prefer wna16 for groupwise weight or groupwise input
|
||||
return HummingInputSchema()
|
||||
|
||||
return input_schema
|
||||
|
||||
if allow_fallback:
|
||||
fallback_dtypes = (
|
||||
a_dtype,
|
||||
*dtype_fallback_order_map.get(a_dtype, ()),
|
||||
humming_dtypes.DataType.from_any(param_dtype),
|
||||
)
|
||||
for dtype in fallback_dtypes:
|
||||
if dtype is None or is_deprecated(dtype):
|
||||
continue
|
||||
|
||||
group_size = 0
|
||||
scale_dtype = humming_dtypes.float32
|
||||
quant_mode = InputQuantizationMode.DynamicToken
|
||||
if dtype.num_bits == 16:
|
||||
scale_dtype = None
|
||||
quant_mode = InputQuantizationMode.Disabled
|
||||
elif dtype in (humming_dtypes.float8e4m3, humming_dtypes.float8e3m4):
|
||||
is_channelwise_weight = weight_schema.weight_scale_group_size == 0
|
||||
weight_group_size = weight_schema.weight_scale_group_size
|
||||
is_e8m0_scale = weight_schema.bs_dtype == humming_dtypes.float8e8m0
|
||||
is_mx_weight = weight_group_size == 32 and is_e8m0_scale
|
||||
if sm_version >= 120 and (is_channelwise_weight or is_mx_weight):
|
||||
group_size = 32
|
||||
scale_dtype = humming_dtypes.float8e8m0
|
||||
quant_mode = InputQuantizationMode.DynamicGroup
|
||||
|
||||
candidate = HummingInputSchema(
|
||||
a_dtype=dtype,
|
||||
input_scale_group_size=group_size,
|
||||
input_scale_dtype=scale_dtype,
|
||||
input_quant_mode=quant_mode,
|
||||
)
|
||||
if candidate.is_compatible_with(weight_schema, param_dtype):
|
||||
return candidate
|
||||
|
||||
raise ValueError(
|
||||
f"No compatible Humming input schema for {input_schema} with "
|
||||
f"weight schema {weight_schema} on SM{sm_version} "
|
||||
f"({allow_fallback=})"
|
||||
)
|
||||
|
||||
|
||||
def resolve_humming_layer_config(config: dict[str, Any], prefix: str) -> dict[str, Any]:
|
||||
layer_config = config.copy()
|
||||
dynamic = config.get("dynamic", {})
|
||||
if isinstance(dynamic, dict):
|
||||
for pattern, overrides in dynamic.items():
|
||||
if pattern.startswith("+") and re.match(pattern[2:], prefix):
|
||||
layer_config.update(overrides)
|
||||
break
|
||||
return layer_config
|
||||
|
||||
|
||||
def humming_is_layer_skipped(config: dict[str, Any], prefix: str):
|
||||
if not config:
|
||||
return True
|
||||
|
||||
keys = ["ignored_layers", "ignore", "modules_to_not_convert"]
|
||||
ignored_layers: list[str] = []
|
||||
for key in keys:
|
||||
candidate = config.get(key, []) or []
|
||||
if candidate:
|
||||
ignored_layers = candidate
|
||||
break
|
||||
|
||||
if any(module_name in prefix for module_name in ignored_layers):
|
||||
return True
|
||||
if "lm_head" in prefix:
|
||||
return True
|
||||
|
||||
for regex in config.get("dynamic", {}):
|
||||
if regex[:1] != "-":
|
||||
continue
|
||||
if re.match(regex[2:], prefix):
|
||||
return True
|
||||
|
||||
return False
|
||||
+52
-3
@@ -4,12 +4,56 @@
|
||||
|
||||
vLLM code should import humming symbols from here so that ``import humming``
|
||||
(which has import-time side effects) is deferred until first use. Add new
|
||||
symbols by appending one entry to ``_EXPORTS`` as ``"module.path:attr"``,
|
||||
or ``"module.path"`` for a whole-module re-export.
|
||||
symbols to both the ``TYPE_CHECKING`` imports and ``_EXPORTS``, using
|
||||
``"module.path:attr"`` or ``"module.path"`` for a whole-module re-export.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from humming import dtypes as dtypes
|
||||
from humming.config import GemmType as GemmType
|
||||
from humming.config import InputQuantizationMode as InputQuantizationMode
|
||||
from humming.config import LayerConfig as LayerConfig
|
||||
from humming.config import MmaType as MmaType
|
||||
from humming.config import WeightScale2Type as WeightScale2Type
|
||||
from humming.config import WeightScaleType as WeightScaleType
|
||||
from humming.dtypes import DataType as DataType
|
||||
from humming.forward import humming_forward as humming_forward
|
||||
from humming.forward import may_process_input as may_process_input
|
||||
from humming.forward import may_quant_input as may_quant_input
|
||||
from humming.ops import process_input as process_input
|
||||
from humming.schema import AWQWeightSchema as AWQWeightSchema
|
||||
from humming.schema import BaseInputSchema as BaseInputSchema
|
||||
from humming.schema import BaseWeightSchema as BaseWeightSchema
|
||||
from humming.schema import BitnetWeightSchema as BitnetWeightSchema
|
||||
from humming.schema import (
|
||||
CompressedTensorsInputSchema as CompressedTensorsInputSchema,
|
||||
)
|
||||
from humming.schema import (
|
||||
CompressedTensorsWeightSchema as CompressedTensorsWeightSchema,
|
||||
)
|
||||
from humming.schema import Fp8InputSchema as Fp8InputSchema
|
||||
from humming.schema import GptOssMxfp4WeightSchema as GptOssMxfp4WeightSchema
|
||||
from humming.schema import GPTQWeightSchema as GPTQWeightSchema
|
||||
from humming.schema import HummingInputSchema as HummingInputSchema
|
||||
from humming.schema import HummingWeightSchema as HummingWeightSchema
|
||||
from humming.schema import Mxfp4WeightSchema as Mxfp4WeightSchema
|
||||
from humming.schema.fp8 import Fp8WeightSchema as Fp8WeightSchema
|
||||
from humming.schema.modelopt import (
|
||||
ModeloptMxfp8WeightSchema as ModeloptMxfp8WeightSchema,
|
||||
)
|
||||
from humming.schema.modelopt import (
|
||||
ModeloptNvfp4InputSchema as ModeloptNvfp4InputSchema,
|
||||
)
|
||||
from humming.schema.modelopt import (
|
||||
ModeloptNvfp4WeightSchema as ModeloptNvfp4WeightSchema,
|
||||
)
|
||||
from humming.transform import prepare_layer_config as prepare_layer_config
|
||||
from humming.transform import transform_humming_tensors as transform_humming_tensors
|
||||
from humming.tune import get_heuristics_config as get_heuristics_config
|
||||
from humming.utils.weight import quantize_weight as quantize_weight
|
||||
|
||||
_EXPORTS: dict[str, str] = {
|
||||
"dtypes": "humming.dtypes",
|
||||
@@ -17,7 +61,12 @@ _EXPORTS: dict[str, str] = {
|
||||
"GemmType": "humming.config:GemmType",
|
||||
"LayerConfig": "humming.config:LayerConfig",
|
||||
"WeightScaleType": "humming.config:WeightScaleType",
|
||||
"WeightScale2Type": "humming.config:WeightScale2Type",
|
||||
"InputQuantizationMode": "humming.config:InputQuantizationMode",
|
||||
"MmaType": "humming.config:MmaType",
|
||||
"humming_forward": "humming.forward:humming_forward",
|
||||
"may_process_input": "humming.forward:may_process_input",
|
||||
"process_input": "humming.ops:process_input",
|
||||
"may_quant_input": "humming.forward:may_quant_input",
|
||||
"prepare_layer_config": "humming.transform:prepare_layer_config",
|
||||
"transform_humming_tensors": "humming.transform:transform_humming_tensors",
|
||||
|
||||
Reference in New Issue
Block a user