HCHs commited on
Commit
4d29b4b
·
verified ·
1 Parent(s): a92180d

Accelerate remaining FP8 linears with direct scaled-mm

Browse files
NOTICE.md CHANGED
@@ -14,7 +14,8 @@ RivetCoder-9B-A4B combines the following sources:
14
  Triton grouped-FP8 and activation-quantization kernels are Copyright 2026 The
15
  Hugging Face Inc. team and licensed under Apache License 2.0. RivetCoder's
16
  adaptation adds per-output scales, TorchAO expert-bank packing, deterministic
17
- Top-4 reduction, and automatic Windows MSVC environment discovery.
 
18
 
19
  Modifications and new work include GLM expert selection, a fixed tied
20
  identity-Hadamard bridge, expert/router weight folding, per-layer Top-4 routing,
 
14
  Triton grouped-FP8 and activation-quantization kernels are Copyright 2026 The
15
  Hugging Face Inc. team and licensed under Apache License 2.0. RivetCoder's
16
  adaptation adds per-output scales, TorchAO expert-bank packing, deterministic
17
+ Top-4 reduction, direct FP8 Linear dispatch using the checkpoint's existing
18
+ qdata/scales, and automatic Windows MSVC environment discovery.
19
 
20
  Modifications and new work include GLM expert selection, a fixed tied
21
  identity-Hadamard bridge, expert/router weight folding, per-layer Top-4 routing,
README.md CHANGED
@@ -133,9 +133,11 @@ still passed quantization, serialization, clean reload, and forward validation.
133
 
134
  The repository includes an inference-only Triton runtime that replaces the
135
  Python 16-expert loop with two grouped FP8 GEMMs per fused layer: one combined
136
- gate/up projection and one down projection. It preserves Top-4 routing and the
137
- reference FP8 logits while releasing the unpacked expert tensors after runtime
138
- packing.
 
 
139
 
140
  For direct Transformers use, enable it after loading:
141
 
@@ -166,8 +168,8 @@ RTX 5070 Ti validation with a one-token full forward produced:
166
 
167
  | Runtime | Latency | Relative throughput |
168
  |---|---:|---:|
169
- | Original TorchAO expert loop | 5.100 s | 1.00x |
170
- | Grouped-FP8 fast path | 1.079 s | 4.73x |
171
 
172
  The logits were bit-exact (`MAE=0`, `max error=0`, identical top-1), repeated
173
  execution was deterministic, and resident VRAM was about 8.43 GiB. With the
@@ -175,14 +177,15 @@ fast path enabled, fixed microbatch throughput scaled as follows:
175
 
176
  | Batch | Forward latency | Sequences/s |
177
  |---:|---:|---:|
178
- | 1 | 1.080 s | 0.93 |
179
- | 4 | 1.087 s | 3.68 |
180
- | 8 | 1.086 s | 7.36 |
181
- | 16 | 1.117 s | 14.33 |
182
 
183
  These are local full-forward measurements, not standardized generation
184
- benchmarks. Batch 16 increased throughput about 15.5x over batch 1 while adding
185
- only about 3.4% latency, which is why the bundled server defaults to batch 16.
 
186
 
187
  ## Limitations
188
 
 
133
 
134
  The repository includes an inference-only Triton runtime that replaces the
135
  Python 16-expert loop with two grouped FP8 GEMMs per fused layer: one combined
136
+ gate/up projection and one down projection. It also bypasses TorchAO's
137
+ tensor-subclass dispatch for 196 remaining compatible FP8 Linear modules and
138
+ calls their existing qdata/scales through `_scaled_mm` directly. It preserves
139
+ Top-4 routing and the reference FP8 logits while releasing the unpacked expert
140
+ tensors after runtime packing.
141
 
142
  For direct Transformers use, enable it after loading:
143
 
 
168
 
169
  | Runtime | Latency | Relative throughput |
170
  |---|---:|---:|
171
+ | Original TorchAO path | 4.894 s | 1.00x |
172
+ | Grouped/direct-FP8 fast path | 0.304 s | 16.08x |
173
 
174
  The logits were bit-exact (`MAE=0`, `max error=0`, identical top-1), repeated
175
  execution was deterministic, and resident VRAM was about 8.43 GiB. With the
 
177
 
178
  | Batch | Forward latency | Sequences/s |
179
  |---:|---:|---:|
180
+ | 1 | 0.337 s | 2.96 |
181
+ | 4 | 0.316 s | 12.65 |
182
+ | 8 | 0.340 s | 23.50 |
183
+ | 16 | 0.309 s | 51.71 |
184
 
185
  These are local full-forward measurements, not standardized generation
186
+ benchmarks. Batch 16 increased throughput about 17.5x over batch 1 without a
187
+ latency increase in this short test, which is why the bundled server defaults
188
+ to batch 16.
189
 
190
  ## Limitations
191
 
fast_fp8_runtime.py CHANGED
@@ -15,6 +15,7 @@ import gc
15
  import os
16
  import shutil
17
  import subprocess
 
18
  from pathlib import Path
19
  from typing import Any
20
 
@@ -324,6 +325,52 @@ def _float8_parts(linear: nn.Linear) -> tuple[torch.Tensor, torch.Tensor]:
324
  return qdata, scale.reshape(())
325
 
326
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  class PackedFp8ExpertBank(nn.Module):
328
  """One layer's 16 experts packed into two grouped FP8 projections."""
329
 
@@ -514,12 +561,14 @@ def install_fast_fp8_runtime(model: nn.Module) -> dict[str, Any]:
514
  # per-expert qdata that the packed banks replaced.
515
  gc.collect()
516
  torch.cuda.empty_cache()
 
517
 
518
  return {
519
  "backend": "triton-grouped-fp8",
520
  "packed_layers": packed_layers,
521
  "released_experts": released_experts,
522
  "packed_bytes": packed_bytes,
 
523
  "cuda_allocated_bytes": torch.cuda.memory_allocated(),
524
  "compiler": os.environ.get("CC"),
525
  }
 
15
  import os
16
  import shutil
17
  import subprocess
18
+ import types
19
  from pathlib import Path
20
  from typing import Any
21
 
 
325
  return qdata, scale.reshape(())
326
 
327
 
328
+ def _direct_fp8_linear_forward(linear: nn.Linear, hidden_states: torch.Tensor) -> torch.Tensor:
329
+ """TorchAO-compatible PerTensor FP8 Linear without tensor-subclass dispatch."""
330
+
331
+ qdata, weight_scale = _float8_parts(linear)
332
+ original_shape = hidden_states.shape
333
+ flattened = hidden_states.reshape(-1, original_shape[-1]).contiguous()
334
+ activation_scale = (flattened.abs().amax() / 448.0).float().reshape(1, 1)
335
+ activation_scale = activation_scale.clamp_min(1.0e-12)
336
+ quantized = (
337
+ flattened.float()
338
+ .div(activation_scale)
339
+ .clamp(min=-448.0, max=448.0)
340
+ .to(torch.float8_e4m3fn)
341
+ )
342
+ output = torch._scaled_mm(
343
+ quantized,
344
+ qdata.t(),
345
+ activation_scale,
346
+ weight_scale.reshape(1, 1),
347
+ out_dtype=linear.weight.dtype,
348
+ use_fast_accum=True,
349
+ )
350
+ if linear.bias is not None:
351
+ output = output + linear.bias
352
+ return output.reshape(*original_shape[:-1], linear.out_features)
353
+
354
+
355
+ def _install_direct_fp8_linears(model: nn.Module) -> int:
356
+ installed = 0
357
+ for module in model.modules():
358
+ if not isinstance(module, nn.Linear) or getattr(module, "_rivet_direct_fp8", False):
359
+ continue
360
+ weight = module.weight
361
+ if (
362
+ "Float8" not in type(weight).__name__
363
+ or getattr(weight, "qdata", None) is None
364
+ or getattr(weight, "scale", None) is None
365
+ or weight.scale.numel() != 1
366
+ ):
367
+ continue
368
+ module.forward = types.MethodType(_direct_fp8_linear_forward, module)
369
+ module._rivet_direct_fp8 = True
370
+ installed += 1
371
+ return installed
372
+
373
+
374
  class PackedFp8ExpertBank(nn.Module):
375
  """One layer's 16 experts packed into two grouped FP8 projections."""
376
 
 
561
  # per-expert qdata that the packed banks replaced.
562
  gc.collect()
563
  torch.cuda.empty_cache()
564
+ direct_fp8_linears = _install_direct_fp8_linears(model)
565
 
566
  return {
567
  "backend": "triton-grouped-fp8",
568
  "packed_layers": packed_layers,
569
  "released_experts": released_experts,
570
  "packed_bytes": packed_bytes,
571
+ "direct_fp8_linears": direct_fp8_linears,
572
  "cuda_allocated_bytes": torch.cuda.memory_allocated(),
573
  "compiler": os.environ.get("CC"),
574
  }
provenance/checksums.sha256 CHANGED
@@ -3,7 +3,7 @@
3
  f3e412383b66fc807c3d0fbe9e6f8a4a58b2638df55d8380e9cab64af30e8675 chat_template.jinja
4
  1b441cb88813e8c01f8a9ae98452adfe655ba680e1964c787583634ecb8cb473 config.json
5
  922dfb636b84d09d2f92ba0c5bc226d00d7b41999c7e9aef1939ccb022192427 configuration_fuse_glm.py
6
- 8ced7754937def623087e269d2a0111125ee21046a47b479dd03c8043eb63a78 fast_fp8_runtime.py
7
  e34e51ccf5a169f0d9cecf8119e9f719132fa0cf5fae7f8b650893821348e62a generation_config.json
8
  4d28ca14dedc0b3d0fcc2b3339f0e79931faa33874f3d24f522183a8fc70068c LICENSE
9
  30b85b6b9659f2e78aa259f8faf5d920a68dee7c9ced3fa6dba1f19f2bc4fca1 licenses/GLM-MIT.txt
@@ -16,12 +16,12 @@ ebb55bc7f9cd7139d3e21c0677f2b2b7f99ec25925837729b0c07a6efa2b0074 model-00004-of
16
  5aec98e4957c1263db1e79f2014f468620ec1b512831256329d8091f762fc61a model-00005-of-00005.safetensors
17
  95b79151fed3d2716210fb3046282a47bf73e2016c0ae59f413b59e221e50fc5 model.safetensors.index.json
18
  200287c00eff2afaa68c9e8355fa8c2c73bcab4c2c79c86fb69c7a12ef885d25 modeling_fuse_glm.py
19
- e70d7940c5bc65ec7dda47699654521ade102a99163903ef0d67646ffc151af2 NOTICE.md
20
  b7d698b9414c814a1d78dce6a414c3340f3cd0cd949fc2c6b192bd2e35688710 provenance/assembly.json
21
  4dbb9025d648edd432e0fcbd6cc1f6dafa96e4aa0e0222171923feb17965aa25 provenance/base-bf16-checksums.sha256
22
  92eda133f175fa573b88b92fca5995518965117e534f3b732c63e64862488ff0 provenance/bridge.json
23
  69ae72f0e5add90269c5e949366c10e87d1de64fc815d14d0f330010f0fd1400 provenance/expert-selection.json
24
- cbcbfc68c1e543df155dbee63fe7eac265ee8b0f0ac4e5f19592da211a3bcae8 provenance/fast-serving.json
25
  036c4343e5d62e72613b3bb0ad1238d9f8000088d1118439798ac7ade13d7a2f provenance/folding.json
26
  2ba3ae47063c1b4a957b8c5983c48d08c0dacb01c5624f856b8414ac7ccfc2c8 provenance/fusion-plan.json
27
  a0ab71b19384546ea62e09d0c55eee623f59b92fff5140f1baa883d2386ff12f provenance/pre-repack-index.json
@@ -32,9 +32,9 @@ ac437e75d524dd58e1c6934ad0838c62e11c9d0954d24873cb17862f958f64a5 provenance/sou
32
  88fd032686145809f3dee3e8082a7a3b1cffe2874661289b59472a56ed5fcc1c provenance/training-data-manifest.json
33
  f426ea62b798bb2340b8d9cfdea2f53f309044dbe9caa061c208e358ddc39ea9 provenance/training-summary.json
34
  19f75f7117ca582e393d9f79452af231f93339c55dfcc6bebd2b953f87ed3152 provenance/validation-metrics.json
35
- 8407f808d4182aefa1316828e855ea6ad8267a1822a939169d6cc94ec6a1f79a README.md
36
  da44e453254bbea8d1e9169c37ebd65abcbad816a0889f0ab2d9037ace351e10 requirements-serve.txt
37
  ce31a666514d30d302472da9387348b4958872f7e307a7ee443ae09f8d1b19b5 requirements.txt
38
- 46da82ff64a9bf9ffa1fcdfcd78a419e0e5497a8eb271233a282c3705db44811 serve.py
39
  14c60d6814b7f64c69711d5c5d5561d3de9cc3896feebf9613ae8a8523a3497d tokenizer_config.json
40
  695be7802a0e4b8a81048f0ff5ebb7fc811a0ba5a6be63dbb24deb5a81096f41 tokenizer.json
 
3
  f3e412383b66fc807c3d0fbe9e6f8a4a58b2638df55d8380e9cab64af30e8675 chat_template.jinja
4
  1b441cb88813e8c01f8a9ae98452adfe655ba680e1964c787583634ecb8cb473 config.json
5
  922dfb636b84d09d2f92ba0c5bc226d00d7b41999c7e9aef1939ccb022192427 configuration_fuse_glm.py
6
+ 3604561de9be361a595cd4dcbfd592d6fe13d0da5f920939f457d6fd93492bcb fast_fp8_runtime.py
7
  e34e51ccf5a169f0d9cecf8119e9f719132fa0cf5fae7f8b650893821348e62a generation_config.json
8
  4d28ca14dedc0b3d0fcc2b3339f0e79931faa33874f3d24f522183a8fc70068c LICENSE
9
  30b85b6b9659f2e78aa259f8faf5d920a68dee7c9ced3fa6dba1f19f2bc4fca1 licenses/GLM-MIT.txt
 
16
  5aec98e4957c1263db1e79f2014f468620ec1b512831256329d8091f762fc61a model-00005-of-00005.safetensors
17
  95b79151fed3d2716210fb3046282a47bf73e2016c0ae59f413b59e221e50fc5 model.safetensors.index.json
18
  200287c00eff2afaa68c9e8355fa8c2c73bcab4c2c79c86fb69c7a12ef885d25 modeling_fuse_glm.py
19
+ 457a3785fb93db67d9ce2161aedbe4de86c240cc7908d01f5693eabbe012776d NOTICE.md
20
  b7d698b9414c814a1d78dce6a414c3340f3cd0cd949fc2c6b192bd2e35688710 provenance/assembly.json
21
  4dbb9025d648edd432e0fcbd6cc1f6dafa96e4aa0e0222171923feb17965aa25 provenance/base-bf16-checksums.sha256
22
  92eda133f175fa573b88b92fca5995518965117e534f3b732c63e64862488ff0 provenance/bridge.json
23
  69ae72f0e5add90269c5e949366c10e87d1de64fc815d14d0f330010f0fd1400 provenance/expert-selection.json
24
+ 40bed73ee90cd26f3da8a0f58d79cd48a73d32af40c269fc56ab592bfba50d4a provenance/fast-serving.json
25
  036c4343e5d62e72613b3bb0ad1238d9f8000088d1118439798ac7ade13d7a2f provenance/folding.json
26
  2ba3ae47063c1b4a957b8c5983c48d08c0dacb01c5624f856b8414ac7ccfc2c8 provenance/fusion-plan.json
27
  a0ab71b19384546ea62e09d0c55eee623f59b92fff5140f1baa883d2386ff12f provenance/pre-repack-index.json
 
32
  88fd032686145809f3dee3e8082a7a3b1cffe2874661289b59472a56ed5fcc1c provenance/training-data-manifest.json
33
  f426ea62b798bb2340b8d9cfdea2f53f309044dbe9caa061c208e358ddc39ea9 provenance/training-summary.json
34
  19f75f7117ca582e393d9f79452af231f93339c55dfcc6bebd2b953f87ed3152 provenance/validation-metrics.json
35
+ 81eee8545dadab653bd492dbcee75ed80099dcee73daf92c5c1ebb99512c426b README.md
36
  da44e453254bbea8d1e9169c37ebd65abcbad816a0889f0ab2d9037ace351e10 requirements-serve.txt
37
  ce31a666514d30d302472da9387348b4958872f7e307a7ee443ae09f8d1b19b5 requirements.txt
38
+ e3a92f56b19700001d1dee2dd5aaee3f33fb66fd2a8e41eda8fe1da7599bbd78 serve.py
39
  14c60d6814b7f64c69711d5c5d5561d3de9cc3896feebf9613ae8a8523a3497d tokenizer_config.json
40
  695be7802a0e4b8a81048f0ff5ebb7fc811a0ba5a6be63dbb24deb5a81096f41 tokenizer.json
provenance/fast-serving.json CHANGED
@@ -20,6 +20,7 @@
20
  "backend": "Triton grouped FP8",
21
  "packed_layers": 30,
22
  "packed_experts": 480,
 
23
  "top_k": 4,
24
  "expert_gemms_per_layer": 2,
25
  "gate_up_shape": [16, 4096, 2048],
@@ -30,9 +31,9 @@
30
  "kernel_license": "Apache-2.0"
31
  },
32
  "full_model_parity": {
33
- "baseline_seconds": 5.100,
34
- "fast_seconds": 1.079,
35
- "speedup": 4.73,
36
  "mean_absolute_logit_error": 0.0,
37
  "maximum_absolute_logit_error": 0.0,
38
  "top1_equal": true,
@@ -41,10 +42,10 @@
41
  "packing_peak_vram_gib": 14.053
42
  },
43
  "microbatch_forward": [
44
- {"batch": 1, "seconds": 1.0800, "sequences_per_second": 0.9259},
45
- {"batch": 4, "seconds": 1.0868, "sequences_per_second": 3.6806},
46
- {"batch": 8, "seconds": 1.0863, "sequences_per_second": 7.3645},
47
- {"batch": 16, "seconds": 1.1169, "sequences_per_second": 14.3259}
48
  ],
49
  "server": {
50
  "protocol": "OpenAI-compatible chat completions",
 
20
  "backend": "Triton grouped FP8",
21
  "packed_layers": 30,
22
  "packed_experts": 480,
23
+ "direct_fp8_linears": 196,
24
  "top_k": 4,
25
  "expert_gemms_per_layer": 2,
26
  "gate_up_shape": [16, 4096, 2048],
 
31
  "kernel_license": "Apache-2.0"
32
  },
33
  "full_model_parity": {
34
+ "baseline_seconds": 4.8937,
35
+ "fast_seconds": 0.3044,
36
+ "speedup": 16.08,
37
  "mean_absolute_logit_error": 0.0,
38
  "maximum_absolute_logit_error": 0.0,
39
  "top1_equal": true,
 
42
  "packing_peak_vram_gib": 14.053
43
  },
44
  "microbatch_forward": [
45
+ {"batch": 1, "seconds": 0.3374, "sequences_per_second": 2.9635},
46
+ {"batch": 4, "seconds": 0.3162, "sequences_per_second": 12.6488},
47
+ {"batch": 8, "seconds": 0.3404, "sequences_per_second": 23.5045},
48
+ {"batch": 16, "seconds": 0.3094, "sequences_per_second": 51.7069}
49
  ],
50
  "server": {
51
  "protocol": "OpenAI-compatible chat completions",
serve.py CHANGED
@@ -134,18 +134,18 @@ class MicrobatchEngine:
134
  ).to("cuda")
135
  prompt_width = encoded["input_ids"].shape[-1]
136
  temperature = batch[0].temperature
 
 
 
 
 
 
 
 
 
 
137
  with torch.no_grad():
138
- generated = self.model.generate(
139
- **encoded,
140
- max_new_tokens=batch[0].max_tokens,
141
- do_sample=temperature > 0,
142
- temperature=max(temperature, 1.0e-5),
143
- top_p=batch[0].top_p,
144
- use_cache=True,
145
- logits_to_keep=1,
146
- pad_token_id=self.tokenizer.pad_token_id,
147
- eos_token_id=self.tokenizer.eos_token_id,
148
- )
149
  return self.tokenizer.batch_decode(
150
  generated[:, prompt_width:],
151
  skip_special_tokens=True,
 
134
  ).to("cuda")
135
  prompt_width = encoded["input_ids"].shape[-1]
136
  temperature = batch[0].temperature
137
+ generation_kwargs = {
138
+ "max_new_tokens": batch[0].max_tokens,
139
+ "do_sample": temperature > 0,
140
+ "use_cache": True,
141
+ "logits_to_keep": 1,
142
+ "pad_token_id": self.tokenizer.pad_token_id,
143
+ "eos_token_id": self.tokenizer.eos_token_id,
144
+ }
145
+ if temperature > 0:
146
+ generation_kwargs.update(temperature=temperature, top_p=batch[0].top_p)
147
  with torch.no_grad():
148
+ generated = self.model.generate(**encoded, **generation_kwargs)
 
 
 
 
 
 
 
 
 
 
149
  return self.tokenizer.batch_decode(
150
  generated[:, prompt_width:],
151
  skip_special_tokens=True,