Text Generation
Transformers
Safetensors
English
Korean
code
fuse_glm
custom_code
lfm2
glm
mixture-of-experts
routed-experts
coding
code-generation
fp8
torchao
top-k-routing
trust-remote-code
conversational
Instructions to use HCHs/RivetCoder-9B-A4B-FP8 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use HCHs/RivetCoder-9B-A4B-FP8 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="HCHs/RivetCoder-9B-A4B-FP8", trust_remote_code=True) messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("HCHs/RivetCoder-9B-A4B-FP8", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use HCHs/RivetCoder-9B-A4B-FP8 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "HCHs/RivetCoder-9B-A4B-FP8" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "HCHs/RivetCoder-9B-A4B-FP8", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/HCHs/RivetCoder-9B-A4B-FP8
- SGLang
How to use HCHs/RivetCoder-9B-A4B-FP8 with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "HCHs/RivetCoder-9B-A4B-FP8" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "HCHs/RivetCoder-9B-A4B-FP8", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "HCHs/RivetCoder-9B-A4B-FP8" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "HCHs/RivetCoder-9B-A4B-FP8", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use HCHs/RivetCoder-9B-A4B-FP8 with Docker Model Runner:
docker model run hf.co/HCHs/RivetCoder-9B-A4B-FP8
| """Inference-only grouped FP8 expert runtime for RivetCoder. | |
| The Triton kernels in this module are adapted from Hugging Face's | |
| ``kernels-community/finegrained-fp8`` package (Apache-2.0). The adaptation | |
| adds per-output weight scales so separately quantized gate/up projections can | |
| be concatenated without requantizing their checkpoint tensors. | |
| This module is intentionally imported lazily. Normal BF16 loading, training, | |
| and CPU execution do not require Triton or TorchAO. | |
| """ | |
| from __future__ import annotations | |
| import gc | |
| import os | |
| import shutil | |
| import subprocess | |
| import types | |
| from pathlib import Path | |
| from typing import Any | |
| import torch | |
| import torch.nn.functional as F | |
| from torch import nn | |
| def ensure_windows_msvc_environment() -> str | None: | |
| """Populate the MSVC environment Triton's Windows launcher JIT needs.""" | |
| if os.name != "nt": | |
| return None | |
| configured = os.environ.get("CC") | |
| if configured and (Path(configured).is_file() or shutil.which(configured)): | |
| return configured | |
| compiler = shutil.which("cl.exe") | |
| if compiler: | |
| os.environ["CC"] = compiler | |
| return compiler | |
| candidates = [ | |
| Path(os.environ.get("ProgramFiles", r"C:\Program Files")) | |
| / "Microsoft Visual Studio" | |
| / "2022" | |
| / edition | |
| / "Common7" | |
| / "Tools" | |
| / "VsDevCmd.bat" | |
| for edition in ("Community", "Professional", "Enterprise", "BuildTools") | |
| ] | |
| vsdevcmd = next((path for path in candidates if path.is_file()), None) | |
| if vsdevcmd is None: | |
| raise RuntimeError( | |
| "Triton needs an MSVC C compiler on Windows. Install Visual Studio 2022 " | |
| "C++ Build Tools or launch the server from a Developer PowerShell." | |
| ) | |
| command = f'call "{vsdevcmd}" -arch=x64 -host_arch=x64 >nul && set' | |
| completed = subprocess.run( | |
| command, | |
| check=True, | |
| capture_output=True, | |
| text=True, | |
| encoding="utf-8", | |
| errors="replace", | |
| shell=True, | |
| executable=os.environ.get("COMSPEC", "cmd.exe"), | |
| ) | |
| for line in completed.stdout.splitlines(): | |
| name, separator, value = line.partition("=") | |
| if separator and name: | |
| os.environ[name] = value | |
| compiler = shutil.which("cl.exe") | |
| if compiler is None: | |
| raise RuntimeError("VsDevCmd completed but cl.exe is still unavailable") | |
| os.environ["CC"] = compiler | |
| return compiler | |
| def _load_triton() -> tuple[Any, Any, Any, Any]: | |
| ensure_windows_msvc_environment() | |
| try: | |
| import triton | |
| import triton.language as tl | |
| from torch.library import triton_op, wrap_triton | |
| except ImportError as error: | |
| raise RuntimeError( | |
| "Fast FP8 serving requires Triton. Use a PyTorch build that bundles Triton " | |
| "or install a Windows-compatible Triton package." | |
| ) from error | |
| return triton, tl, triton_op, wrap_triton | |
| triton, tl, triton_op, wrap_triton = _load_triton() | |
| def _fp8_per_row_quant_kernel(x_ptr, q_ptr, scale_ptr, K: tl.constexpr): | |
| row = tl.program_id(axis=0) | |
| offsets = tl.arange(0, K) | |
| values = tl.load(x_ptr + row * K + offsets).to(tl.float32) | |
| scale = tl.maximum(tl.max(tl.abs(values), axis=0) / 448.0, 1.0e-12) | |
| quantized = (values / scale).to(tl.float8e4nv) | |
| tl.store(q_ptr + row * K + offsets, quantized) | |
| tl.store(scale_ptr + row, scale) | |
| def _fp8_per_row_quant(x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: | |
| if x.ndim != 2 or not x.is_contiguous(): | |
| raise ValueError("FP8 activation input must be a contiguous 2D tensor") | |
| if x.shape[1] <= 0 or x.shape[1] & (x.shape[1] - 1): | |
| raise ValueError("FP8 activation width must be a positive power of two") | |
| quantized = torch.empty_like(x, dtype=torch.float8_e4m3fn) | |
| scales = torch.empty(x.shape[0], device=x.device, dtype=torch.float32) | |
| wrap_triton(_fp8_per_row_quant_kernel)[(x.shape[0],)]( | |
| x, | |
| quantized, | |
| scales, | |
| K=x.shape[1], | |
| ) | |
| return quantized, scales | |
| def _grouped_fp8_linear_kernel( | |
| A, | |
| B, | |
| C, | |
| AScales, | |
| BScales, | |
| Offsets, | |
| TileOffsets, | |
| S, | |
| N: tl.constexpr, | |
| K: tl.constexpr, | |
| stride_am, | |
| stride_ak, | |
| stride_be, | |
| stride_bk, | |
| stride_bn, | |
| stride_cm, | |
| stride_cn, | |
| stride_bs_e, | |
| stride_bs_n, | |
| NUM_EXPERTS: tl.constexpr, | |
| BLOCK_N: tl.constexpr, | |
| BLOCK_K: tl.constexpr, | |
| BLOCK_M: tl.constexpr, | |
| SEARCH_STEPS: tl.constexpr, | |
| ): | |
| tile_m = tl.program_id(axis=0) | |
| tile_n = tl.program_id(axis=1) | |
| total_tiles = tl.load(TileOffsets + NUM_EXPERTS - 1) | |
| if tile_m >= total_tiles: | |
| return | |
| low = 0 | |
| high = NUM_EXPERTS | |
| for _ in tl.static_range(SEARCH_STEPS): | |
| middle = (low + high) >> 1 | |
| middle_value = tl.load(TileOffsets + middle) | |
| move_right = middle_value <= tile_m | |
| low = tl.where(move_right, middle + 1, low) | |
| high = tl.where(move_right, high, middle) | |
| expert = low.to(tl.int64) | |
| previous = tl.maximum(expert - 1, 0) | |
| expert_start = tl.where(expert == 0, 0, tl.load(Offsets + previous)) | |
| expert_end = tl.load(Offsets + expert) | |
| expert_rows = expert_end - expert_start | |
| expert_tile_start = tl.where(expert == 0, 0, tl.load(TileOffsets + previous)) | |
| local_row_start = (tile_m - expert_tile_start) * BLOCK_M | |
| row_offsets = local_row_start + tl.arange(0, BLOCK_M) | |
| valid_rows = row_offsets < expert_rows | |
| global_rows = expert_start + row_offsets | |
| output_offsets = tile_n * BLOCK_N + tl.arange(0, BLOCK_N) | |
| k_offsets = tl.arange(0, BLOCK_K) | |
| a_ptrs = A + global_rows[:, None] * stride_am + k_offsets[None, :] * stride_ak | |
| b_ptrs = ( | |
| B | |
| + expert * stride_be | |
| + output_offsets[None, :] * stride_bn | |
| + k_offsets[:, None] * stride_bk | |
| ) | |
| accumulator = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32) | |
| for _ in range(0, tl.cdiv(K, BLOCK_K)): | |
| a = tl.load(a_ptrs, mask=valid_rows[:, None], other=0.0) | |
| b = tl.load(b_ptrs) | |
| accumulator += tl.dot(a, b) | |
| a_ptrs += BLOCK_K * stride_ak | |
| b_ptrs += BLOCK_K * stride_bk | |
| activation_scale = tl.load( | |
| AScales + global_rows, | |
| mask=valid_rows, | |
| other=0.0, | |
| ) | |
| weight_scale = tl.load( | |
| BScales + expert * stride_bs_e + output_offsets * stride_bs_n, | |
| ) | |
| accumulator *= activation_scale[:, None] * weight_scale[None, :] | |
| if C.dtype.element_ty == tl.bfloat16: | |
| result = accumulator.to(tl.bfloat16) | |
| elif C.dtype.element_ty == tl.float16: | |
| result = accumulator.to(tl.float16) | |
| else: | |
| result = accumulator | |
| c_ptrs = C + global_rows[:, None] * stride_cm + output_offsets[None, :] * stride_cn | |
| tl.store(c_ptrs, result, mask=valid_rows[:, None]) | |
| def _grouped_fp8_linear( | |
| activations: torch.Tensor, | |
| weights: torch.Tensor, | |
| weight_scales: torch.Tensor, | |
| offsets: torch.Tensor, | |
| tokens_per_expert: torch.Tensor, | |
| ) -> torch.Tensor: | |
| if activations.ndim != 2 or not activations.is_contiguous(): | |
| raise ValueError("activations must be contiguous [routes, hidden]") | |
| if weights.ndim != 3 or not weights.is_contiguous(): | |
| raise ValueError("weights must be contiguous [experts, output, hidden]") | |
| if weights.dtype != torch.float8_e4m3fn: | |
| raise TypeError("weights must use torch.float8_e4m3fn") | |
| experts, output_size, hidden_size = weights.shape | |
| if activations.shape[1] != hidden_size: | |
| raise ValueError("activation/weight hidden dimensions do not match") | |
| if output_size % 128 or hidden_size % 128: | |
| raise ValueError("grouped FP8 output and hidden dimensions must be divisible by 128") | |
| if weight_scales.shape != (experts, output_size): | |
| raise ValueError("weight_scales must have shape [experts, output]") | |
| if offsets.shape != (experts,) or tokens_per_expert.shape != (experts,): | |
| raise ValueError("offset/count tensors must have one value per expert") | |
| # TorchAO's checkpoint config uses dynamic PerTensor activation scaling. | |
| # Match that per routed expert (rather than per row) so prefill follows the | |
| # same quantization semantics as the original expert-by-expert calls. | |
| expert_ids = torch.repeat_interleave( | |
| torch.arange(experts, device=activations.device), | |
| tokens_per_expert.to(torch.long), | |
| output_size=activations.shape[0], | |
| ) | |
| row_max = activations.abs().amax(dim=-1) | |
| expert_max = torch.zeros(experts, device=activations.device, dtype=activations.dtype) | |
| expert_max.scatter_reduce_(0, expert_ids, row_max, reduce="amax", include_self=True) | |
| expert_scales = (expert_max / 448.0).float().clamp_min(1.0e-12) | |
| activation_scales = expert_scales.index_select(0, expert_ids).contiguous() | |
| quantized = ( | |
| activations.float() | |
| .div(activation_scales.unsqueeze(-1)) | |
| .clamp(min=-448.0, max=448.0) | |
| .to(torch.float8_e4m3fn) | |
| ) | |
| output = activations.new_empty((activations.shape[0], output_size)) | |
| block_m = min(max(triton.next_power_of_2((activations.shape[0] + experts - 1) // experts), 16), 128) | |
| tiles_per_expert = (tokens_per_expert + block_m - 1) // block_m | |
| tile_offsets = torch.cumsum(tiles_per_expert, dim=0, dtype=torch.int32) | |
| max_m_tiles = triton.cdiv(activations.shape[0], block_m) + experts | |
| grid = (max_m_tiles, triton.cdiv(output_size, 128)) | |
| wrap_triton(_grouped_fp8_linear_kernel)[grid]( | |
| quantized, | |
| weights, | |
| output, | |
| activation_scales, | |
| weight_scales, | |
| offsets, | |
| tile_offsets, | |
| activations.shape[0], | |
| output_size, | |
| hidden_size, | |
| quantized.stride(0), | |
| quantized.stride(1), | |
| weights.stride(0), | |
| weights.stride(2), | |
| weights.stride(1), | |
| output.stride(0), | |
| output.stride(1), | |
| weight_scales.stride(0), | |
| weight_scales.stride(1), | |
| NUM_EXPERTS=experts, | |
| BLOCK_N=128, | |
| BLOCK_K=128, | |
| BLOCK_M=block_m, | |
| SEARCH_STEPS=experts.bit_length(), | |
| ) | |
| return output | |
| def grouped_fp8_linear( | |
| activations: torch.Tensor, | |
| weights: torch.Tensor, | |
| weight_scales: torch.Tensor, | |
| offsets: torch.Tensor, | |
| tokens_per_expert: torch.Tensor, | |
| ) -> torch.Tensor: | |
| return torch.ops.rivet_fp8.grouped_linear( | |
| activations, | |
| weights, | |
| weight_scales, | |
| offsets, | |
| tokens_per_expert, | |
| ) | |
| def _float8_parts(linear: nn.Linear) -> tuple[torch.Tensor, torch.Tensor]: | |
| weight = linear.weight | |
| qdata = getattr(weight, "qdata", None) | |
| scale = getattr(weight, "scale", None) | |
| if qdata is None or scale is None or "Float8" not in type(weight).__name__: | |
| raise TypeError("fast serving requires TorchAO Float8Tensor Linear weights") | |
| if qdata.dtype != torch.float8_e4m3fn or scale.numel() != 1: | |
| raise TypeError("fast serving currently supports per-tensor E4M3 TorchAO weights") | |
| return qdata, scale.reshape(()) | |
| def _direct_fp8_linear_forward(linear: nn.Linear, hidden_states: torch.Tensor) -> torch.Tensor: | |
| """TorchAO-compatible PerTensor FP8 Linear without tensor-subclass dispatch.""" | |
| qdata, weight_scale = _float8_parts(linear) | |
| original_shape = hidden_states.shape | |
| flattened = hidden_states.reshape(-1, original_shape[-1]).contiguous() | |
| activation_scale = (flattened.abs().amax() / 448.0).float().reshape(1, 1) | |
| activation_scale = activation_scale.clamp_min(1.0e-12) | |
| quantized = ( | |
| flattened.float() | |
| .div(activation_scale) | |
| .clamp(min=-448.0, max=448.0) | |
| .to(torch.float8_e4m3fn) | |
| ) | |
| output = torch._scaled_mm( | |
| quantized, | |
| qdata.t(), | |
| activation_scale, | |
| weight_scale.reshape(1, 1), | |
| out_dtype=linear.weight.dtype, | |
| use_fast_accum=True, | |
| ) | |
| if linear.bias is not None: | |
| output = output + linear.bias | |
| return output.reshape(*original_shape[:-1], linear.out_features) | |
| def _install_direct_fp8_linears(model: nn.Module) -> int: | |
| installed = 0 | |
| for module in model.modules(): | |
| if not isinstance(module, nn.Linear) or getattr(module, "_rivet_direct_fp8", False): | |
| continue | |
| weight = module.weight | |
| if ( | |
| "Float8" not in type(weight).__name__ | |
| or getattr(weight, "qdata", None) is None | |
| or getattr(weight, "scale", None) is None | |
| or weight.scale.numel() != 1 | |
| ): | |
| continue | |
| module.forward = types.MethodType(_direct_fp8_linear_forward, module) | |
| module._rivet_direct_fp8 = True | |
| installed += 1 | |
| return installed | |
| class PackedFp8ExpertBank(nn.Module): | |
| """One layer's 16 experts packed into two grouped FP8 projections.""" | |
| def __init__( | |
| self, | |
| gate_up_qdata: torch.Tensor, | |
| gate_up_scales: torch.Tensor, | |
| down_qdata: torch.Tensor, | |
| down_scales: torch.Tensor, | |
| *, | |
| gate_clamp_max: float, | |
| up_clamp_min: float, | |
| up_clamp_max: float, | |
| ) -> None: | |
| super().__init__() | |
| self.register_buffer("gate_up_qdata", gate_up_qdata, persistent=False) | |
| self.register_buffer("gate_up_scales", gate_up_scales, persistent=False) | |
| self.register_buffer("down_qdata", down_qdata, persistent=False) | |
| self.register_buffer("down_scales", down_scales, persistent=False) | |
| self.num_experts = int(gate_up_qdata.shape[0]) | |
| self.intermediate_size = int(gate_up_qdata.shape[1] // 2) | |
| self.hidden_size = int(gate_up_qdata.shape[2]) | |
| self.gate_clamp_max = float(gate_clamp_max) | |
| self.up_clamp_min = float(up_clamp_min) | |
| self.up_clamp_max = float(up_clamp_max) | |
| def from_experts( | |
| cls, | |
| experts: nn.ModuleList, | |
| *, | |
| gate_clamp_max: float, | |
| up_clamp_min: float, | |
| up_clamp_max: float, | |
| ) -> "PackedFp8ExpertBank": | |
| if not experts: | |
| raise ValueError("cannot pack an empty expert list") | |
| first_gate, _ = _float8_parts(experts[0].gate_proj) | |
| first_down, _ = _float8_parts(experts[0].down_proj) | |
| num_experts = len(experts) | |
| intermediate_size, hidden_size = first_gate.shape | |
| if tuple(first_down.shape) != (hidden_size, intermediate_size): | |
| raise ValueError("unexpected down projection shape") | |
| device = first_gate.device | |
| gate_up_qdata = torch.empty( | |
| (num_experts, 2 * intermediate_size, hidden_size), | |
| device=device, | |
| dtype=torch.float8_e4m3fn, | |
| ) | |
| gate_up_scales = torch.empty( | |
| (num_experts, 2 * intermediate_size), device=device, dtype=torch.float32 | |
| ) | |
| down_qdata = torch.empty( | |
| (num_experts, hidden_size, intermediate_size), | |
| device=device, | |
| dtype=torch.float8_e4m3fn, | |
| ) | |
| down_scales = torch.empty((num_experts, hidden_size), device=device, dtype=torch.float32) | |
| for index, expert in enumerate(experts): | |
| gate_qdata, gate_scale = _float8_parts(expert.gate_proj) | |
| up_qdata, up_scale = _float8_parts(expert.up_proj) | |
| down_expert_qdata, down_scale = _float8_parts(expert.down_proj) | |
| if tuple(gate_qdata.shape) != (intermediate_size, hidden_size): | |
| raise ValueError("expert gate projection shapes are inconsistent") | |
| if tuple(up_qdata.shape) != (intermediate_size, hidden_size): | |
| raise ValueError("expert up projection shapes are inconsistent") | |
| if tuple(down_expert_qdata.shape) != (hidden_size, intermediate_size): | |
| raise ValueError("expert down projection shapes are inconsistent") | |
| gate_up_qdata[index, :intermediate_size].copy_(gate_qdata) | |
| gate_up_qdata[index, intermediate_size:].copy_(up_qdata) | |
| gate_up_scales[index, :intermediate_size].copy_(gate_scale.expand(intermediate_size)) | |
| gate_up_scales[index, intermediate_size:].copy_(up_scale.expand(intermediate_size)) | |
| down_qdata[index].copy_(down_expert_qdata) | |
| down_scales[index].copy_(down_scale.expand(hidden_size)) | |
| return cls( | |
| gate_up_qdata, | |
| gate_up_scales, | |
| down_qdata, | |
| down_scales, | |
| gate_clamp_max=gate_clamp_max, | |
| up_clamp_min=up_clamp_min, | |
| up_clamp_max=up_clamp_max, | |
| ) | |
| def forward( | |
| self, | |
| hidden_states: torch.Tensor, | |
| selected_indices: torch.Tensor, | |
| selected_weights: torch.Tensor, | |
| ) -> torch.Tensor: | |
| token_count = hidden_states.shape[0] | |
| route_experts = selected_indices.reshape(-1) | |
| route_tokens = torch.arange(token_count, device=hidden_states.device).repeat_interleave( | |
| selected_indices.shape[-1] | |
| ) | |
| order = torch.argsort(route_experts, stable=True) | |
| sorted_experts = route_experts.index_select(0, order) | |
| sorted_tokens = route_tokens.index_select(0, order) | |
| sorted_hidden = hidden_states.index_select(0, sorted_tokens).contiguous() | |
| sorted_route_weights = selected_weights.reshape(-1).index_select(0, order) | |
| tokens_per_expert = torch.bincount( | |
| sorted_experts, minlength=self.num_experts | |
| ).to(dtype=torch.int32) | |
| offsets = torch.cumsum(tokens_per_expert, dim=0, dtype=torch.int32) | |
| gate_up = grouped_fp8_linear( | |
| sorted_hidden, | |
| self.gate_up_qdata, | |
| self.gate_up_scales, | |
| offsets, | |
| tokens_per_expert, | |
| ) | |
| gate, up = gate_up.split(self.intermediate_size, dim=-1) | |
| intermediate = F.silu(gate.clamp(max=self.gate_clamp_max)) * up.clamp( | |
| min=self.up_clamp_min, | |
| max=self.up_clamp_max, | |
| ) | |
| routed = grouped_fp8_linear( | |
| intermediate.contiguous(), | |
| self.down_qdata, | |
| self.down_scales, | |
| offsets, | |
| tokens_per_expert, | |
| ) | |
| weighted = routed * sorted_route_weights.to(routed.dtype).unsqueeze(-1) | |
| # ``order`` is a permutation, so restoring route order and reducing a | |
| # contiguous [token, top_k, hidden] view avoids duplicate-index atomics. | |
| # This makes decode deterministic and is friendlier to CUDA graphs. | |
| inverse_order = torch.argsort(order) | |
| route_outputs = weighted.index_select(0, inverse_order).reshape( | |
| token_count, selected_indices.shape[-1], self.hidden_size | |
| ) | |
| # The reference dispatcher visits experts in ascending expert-index | |
| # order. Preserve that BF16 accumulation order to minimize long-stack | |
| # drift across 30 residual layers. | |
| expert_order = torch.argsort(selected_indices, dim=-1, stable=True) | |
| route_outputs = route_outputs.gather( | |
| 1, | |
| expert_order.unsqueeze(-1).expand(-1, -1, self.hidden_size), | |
| ) | |
| output = torch.zeros_like(hidden_states) | |
| for route_slot in range(selected_indices.shape[-1]): | |
| output = output + route_outputs[:, route_slot] | |
| return output | |
| def install_fast_fp8_runtime(model: nn.Module) -> dict[str, Any]: | |
| """Pack every expert layer and enable the inference-only fast path. | |
| The transformation releases the original per-expert modules to avoid | |
| duplicating their FP8 storage. It is intentionally one-way for the current | |
| process; reload the checkpoint to recover trainable/module-list form. | |
| """ | |
| if model.training: | |
| raise RuntimeError("call model.eval() before enabling fast FP8 serving") | |
| if not torch.cuda.is_available(): | |
| raise RuntimeError("fast FP8 serving requires CUDA") | |
| ensure_windows_msvc_environment() | |
| wrappers = tuple(model.fusion_layers()) | |
| packed_layers = 0 | |
| released_experts = 0 | |
| packed_bytes = 0 | |
| for wrapper in wrappers: | |
| if getattr(wrapper, "fast_expert_bank", None) is not None: | |
| continue | |
| bank = PackedFp8ExpertBank.from_experts( | |
| wrapper.experts, | |
| gate_clamp_max=wrapper.experts[0].gate_clamp_max, | |
| up_clamp_min=wrapper.experts[0].up_clamp_min, | |
| up_clamp_max=wrapper.experts[0].up_clamp_max, | |
| ) | |
| released_experts += len(wrapper.experts) | |
| packed_bytes += sum(buffer.numel() * buffer.element_size() for buffer in bank.buffers()) | |
| wrapper.fast_expert_bank = bank | |
| wrapper.experts = nn.ModuleList() | |
| wrapper.serving_mode = True | |
| wrapper.last_router_state = None | |
| wrapper.last_router_diagnostics = None | |
| packed_layers += 1 | |
| # TorchAO tensor subclasses can participate in reference cycles. Explicit | |
| # collection is necessary before the allocator can release the unpacked | |
| # per-expert qdata that the packed banks replaced. | |
| gc.collect() | |
| torch.cuda.empty_cache() | |
| direct_fp8_linears = _install_direct_fp8_linears(model) | |
| return { | |
| "backend": "triton-grouped-fp8", | |
| "packed_layers": packed_layers, | |
| "released_experts": released_experts, | |
| "packed_bytes": packed_bytes, | |
| "direct_fp8_linears": direct_fp8_linears, | |
| "cuda_allocated_bytes": torch.cuda.memory_allocated(), | |
| "compiler": os.environ.get("CC"), | |
| } | |