Skip to content

Commit 1270cef

Browse files
authored
Merge pull request #29 from PrimeIntellect-ai/int2
Int2
2 parents 8d7d5f3 + fba601b commit 1270cef

32 files changed

Lines changed: 1417 additions & 1563 deletions

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,14 @@ if (${IS_AMD64})
3636
src/amd64/kernel_amd64_sse42.cpp
3737
src/amd64/kernel_amd64_avx2.cpp
3838
src/amd64/kernel_amd64_avx512f.cpp
39+
src/amd64/kernel_amd64_avx512f_bf16.cpp
3940
)
4041
set(QUANT_SOURCES ${QUANT_SOURCES} ${QUANT_SOURCES_AMD64})
4142

4243
set_file_opts("amd64/kernel_amd64_sse42.cpp" "-mtune=nehalem -msse4.2" "/arch:SSE4.2")
4344
set_file_opts("amd64/kernel_amd64_avx2.cpp" "-mtune=skylake -mavx -mavx2 -mfma -mf16c" "/arch:AVX2")
4445
set_file_opts("amd64/kernel_amd64_avx512f.cpp" "-mtune=cannonlake -mavx -mavx2 -mfma -mf16c -mavx512f -mavx512bw" "/arch:AVX512")
46+
set_file_opts("amd64/kernel_amd64_avx512f_bf16.cpp" "-mtune=cannonlake -mavx -mavx2 -mfma -mf16c -mavx512f -mavx512bf16" "/arch:AVX512")
4547

4648
endif()
4749

benchmark/bench.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ auto main() -> int {
2727
piquant::context ctx {nt};
2828
bench.run("requantize", [&] {
2929
ctx.quantize_generic<float, piquant::uint4_t>(data_in, data_out, 0.2f, 127, piquant::round_mode::nearest);
30-
ctx.dequantize_generic<piquant::uint4_t, float>(data_out, data_in, 0.2f, 127, piquant::reduce_op::add);
3130
});
3231
return 0;
3332
}

include/piquant.h

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
/* Minimal C99 API */
1+
/* Minimal C99 API used from the Python CFFI bindings but also useable from normal C.
2+
* For docs / a more complete C++ API, see piquant.hpp.
3+
*/
24

35
#ifndef PIQUANT_H
46
#define PIQUANT_H
@@ -16,33 +18,25 @@ extern "C" {
1618
#define PIQUANT_EXPORT __attribute__((visibility("default")))
1719
#endif
1820

19-
typedef struct piquant_context_t piquant_context_t; /* Opaque context ptr */
21+
typedef struct piquant_context_t piquant_context_t;
2022

2123
typedef enum piquant_round_mode_t {
2224
PIQUANT_NEAREST,
2325
PIQUANT_STOCHASTIC
2426
} piquant_round_mode_t;
2527

2628
typedef enum piquant_reduce_op_t {
27-
PIQUANT_REDUCE_OP_SET, /* output[i] = quantize(input[i]) */
28-
PIQUANT_REDUCE_OP_ADD, /* output[i] += quantize(input[i]) */
29+
PIQUANT_REDUCE_OP_SET,
30+
PIQUANT_REDUCE_OP_ADD,
2931
} piquant_reduce_op_t;
3032

31-
typedef enum piquant_dtype_t { // Order must match dtype enum class in piquant.hpp
33+
typedef enum piquant_dtype_t {
3234
PIQUANT_DTYPE_F32 = 0,
33-
PIQUANT_DTYPE_F64,
35+
PIQUANT_DTYPE_BF16,
36+
3437
PIQUANT_DTYPE_UINT2,
35-
PIQUANT_DTYPE_INT2,
3638
PIQUANT_DTYPE_UINT4,
37-
PIQUANT_DTYPE_INT4,
38-
PIQUANT_DTYPE_UINT8,
39-
PIQUANT_DTYPE_INT8,
40-
PIQUANT_DTYPE_UINT16,
41-
PIQUANT_DTYPE_INT16,
42-
PIQUANT_DTYPE_UINT32,
43-
PIQUANT_DTYPE_INT32,
44-
PIQUANT_DTYPE_UINT64,
45-
PIQUANT_DTYPE_INT64,
39+
PIQUANT_DTYPE_UINT8
4640
} piquant_dtype_t;
4741

4842
extern PIQUANT_EXPORT piquant_context_t* piquant_context_create(size_t num_threads);
@@ -72,8 +66,7 @@ extern PIQUANT_EXPORT void piquant_dequantize(
7266
piquant_reduce_op_t op
7367
);
7468

75-
/* computes and returns {scale, zero_point} derived from the data's mean and stddev. */
76-
extern PIQUANT_EXPORT void piquant_compute_quant_config_from_data(
69+
extern PIQUANT_EXPORT void piquant_compute_quant_params_float32(
7770
piquant_context_t* ctx,
7871
const float* x,
7972
size_t n,
@@ -82,6 +75,15 @@ extern PIQUANT_EXPORT void piquant_compute_quant_config_from_data(
8275
int64_t* out_zero_point
8376
);
8477

78+
extern PIQUANT_EXPORT void piquant_compute_quant_params_bfloat16(
79+
piquant_context_t* ctx,
80+
const uint16_t* x,
81+
size_t n,
82+
piquant_dtype_t target_quant_dtype,
83+
float* out_scale,
84+
int64_t* out_zero_point
85+
);
86+
8587
#ifdef __cplusplus
8688
}
8789
#endif

include/piquant.hpp

Lines changed: 97 additions & 95 deletions
Large diffs are not rendered by default.

python/benchmark/benchmark.py

Lines changed: 56 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,71 +2,80 @@
22
import timeit
33
import multiprocessing
44

5-
from piquant import QuantDtype
5+
os.environ['CUDA_VISIBLE_DEVICES'] = '' # Disable CUDA
6+
os.environ['OMP_NUM_THREADS'] = str(multiprocessing.cpu_count()) # OpenMP
7+
os.environ['MKL_NUM_THREADS'] = str(multiprocessing.cpu_count()) # MKL
68

7-
os.environ['CUDA_VISIBLE_DEVICES'] = '' # Force CPU usage
8-
os.environ['OMP_NUM_THREADS'] = str(multiprocessing.cpu_count())
9-
os.environ['MKL_NUM_THREADS'] = str(multiprocessing.cpu_count())
10-
11-
import torch
12-
from torch.ao.quantization.fx._decomposed import quantize_per_tensor
139
import piquant
10+
import torch
11+
import numpy as np
1412
import matplotlib.pyplot as plt
1513

16-
num_runs = 1000
17-
numel = 27264000 # Value from realistic test
18-
14+
torch.set_num_threads(multiprocessing.cpu_count())
1915

20-
def quantize_torch_fx(tensor: torch.Tensor, scale: int, zero_point: float) -> None:
21-
return quantize_per_tensor(
22-
tensor, scale=scale, zero_point=zero_point, quant_min=0, quant_max=255, dtype=torch.uint8
23-
)
24-
25-
26-
def quantize_torch_builtin(tensor: torch.Tensor, scale: int, zero_point: float) -> None:
27-
return torch.quantize_per_tensor(tensor, scale=scale, zero_point=zero_point, dtype=torch.quint8).int_repr()
16+
NUM_RUNS: int = 1_000
17+
NUMEL: int = 1000000
2818

19+
QUANT_DTYPES_TO_BENCH: list[torch.dtype] = [
20+
torch.quint8,
21+
torch.quint4x2,
22+
]
2923

30-
def quantize_fast(tensor: torch.Tensor, scale: int, zero_point: float) -> None:
31-
return piquant.quantize_torch(
32-
tensor, config=piquant.QuantConfig(output_dtype=piquant.QuantDtype.UINT8, scale=scale, zero_point=zero_point)
33-
)
24+
def quantize_torch(t: torch.Tensor, scale: float, zp: int, dtype: torch.dtype) -> torch.tensor:
25+
return torch.quantize_per_tensor(t, scale=scale, zero_point=zp, dtype=dtype)
3426

3527

36-
tensor = torch.rand(numel, device='cpu')
37-
scale, zero_point = piquant.compute_quant_config_torch(tensor, target_quant_dtype=QuantDtype.UINT8)
28+
def quantize_piquant(t: torch.Tensor, scale: float, zp: int, dtype: torch.dtype) -> torch.tensor:
29+
return piquant.torch.quantize(t, scale=scale, zero_point=zp, dtype=dtype)
3830

3931

40-
def benchmark_torch_fx_quant() -> None:
41-
quantize_torch_fx(tensor, scale, zero_point)
32+
dtype_labels: list[str] = []
33+
torch_times: list[float] = []
34+
piquant_times: list[float] = []
4235

36+
for torch_d in QUANT_DTYPES_TO_BENCH:
37+
tensor = torch.rand(NUMEL, dtype=torch.float32, device='cpu')
38+
torch_results = []
39+
results_piquant = []
4340

44-
def benchmark_torch_quant() -> None:
45-
quantize_torch_builtin(tensor, scale, zero_point)
41+
scale, zp = piquant.torch.compute_quant_params(tensor, dtype=torch_d)
42+
zp = int(zp)
4643

44+
def _bench_torch() -> None:
45+
torch_results.append(quantize_torch(tensor, scale, zp, torch_d))
4746

48-
def benchmark_fast_quant() -> None:
49-
quantize_fast(tensor, scale, zero_point)
47+
def _bench_piquant() -> None:
48+
results_piquant.append(quantize_piquant(tensor, scale, zp, torch_d))
5049

50+
# Warmup runs
51+
_bench_torch()
52+
_bench_piquant()
5153

52-
time_torch_fx = timeit.timeit(benchmark_torch_fx_quant, number=num_runs)
53-
time_torch = timeit.timeit(benchmark_torch_quant, number=num_runs)
54-
time_fast = timeit.timeit(benchmark_fast_quant, number=num_runs)
54+
torch_time = timeit.timeit(_bench_torch, number=NUM_RUNS)
55+
piquant_time = timeit.timeit(_bench_piquant, number=NUM_RUNS)
56+
dtype_labels.append(str(torch_d).replace('torch.', ''))
57+
torch_times.append(torch_time)
58+
piquant_times.append(piquant_time)
5559

56-
print(f'Torch FX quantization time for {num_runs} runs: {time_torch_fx:.6f} seconds')
57-
print(f'Torch quantization time for {num_runs} runs: {time_torch:.6f} seconds')
58-
print(f'Fast quantization time for {num_runs} runs: {time_fast:.6f} seconds')
60+
# Verify that the results are the same
61+
for i in range(NUM_RUNS): # We compare dequantized results, because .int_repr() is implemented for packed types in torch
62+
dq_torch = torch_results[i].dequantize()
63+
dq_piquant = piquant.torch.dequantize(results_piquant[i], scale=scale, zero_point=zp, dtype=torch.float32)
64+
assert dq_torch.numel() == dq_piquant.numel()
65+
assert dq_torch.dtype == dq_piquant.dtype
66+
assert torch.allclose(dq_torch, dq_piquant, atol=1e-1)
67+
print(f'{dtype_labels[-1]:<10} | torch: {torch_time:.6f}s | piquant: {piquant_time:.6f}s')
5968

60-
labels = [
61-
'Torch FX Quantize',
62-
'Torch Quantize',
63-
'piquant',
64-
]
65-
times = [time_torch_fx, time_torch, time_fast]
6669

67-
plt.figure(figsize=(6, 4))
68-
plt.bar(labels, times)
69-
plt.ylabel(f'Time (seconds) for {num_runs} runs')
70-
plt.title('Quantization Benchmark')
71-
plt.savefig('benchmark.png', dpi=300)
70+
x = np.arange(len(dtype_labels))
71+
width = 0.35
72+
plt.figure(figsize=(8, 5))
73+
plt.bar(x - width / 2, torch_times, width, label='torch')
74+
plt.bar(x + width / 2, piquant_times, width, label='piquant')
75+
plt.ylabel(f'Total time for {NUM_RUNS} runs (s)')
76+
plt.xticks(x, dtype_labels)
77+
plt.title('Quantization Benchmark: PyTorch vs. piquant')
78+
plt.legend()
79+
plt.tight_layout()
80+
plt.savefig('quant_benchmark.png', dpi=300)
7281
plt.show()

python/example/example.py

Lines changed: 0 additions & 19 deletions
This file was deleted.

python/example/example_torch.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import torch
2+
import piquant
3+
4+
# Quantize and back with: bfloat16 -> uint4 -> bfloat16
5+
# In torch, quint4x2 means two 4-bit quantized integers per byte.
6+
tensor = torch.rand(1000, dtype=torch.bfloat16, device='cpu')
7+
8+
# Compute quantization parameters for uint4 (needed for quantization and dequantization)
9+
scale, zero_point = piquant.torch.compute_quant_params(tensor, dtype=torch.quint4x2)
10+
11+
# Quantize the tensor to uint4
12+
quantized = piquant.torch.quantize(tensor, scale=scale, zero_point=zero_point, dtype=torch.quint4x2)
13+
14+
# Dequantize back to bfloat16
15+
dequantized = piquant.torch.dequantize(quantized, scale=scale, zero_point=zero_point, dtype=torch.bfloat16)
16+
17+
# Check if the dequantized tensor is close to the original tensor
18+
assert torch.allclose(dequantized, tensor, atol=scale/2 + 1e-3), "Dequantization did not match original tensor"
19+
20+
# Print parts of original and dequantized tensors for verification
21+
print("Original tensor (first 10 elements):", tensor[:10].tolist())
22+
print("Dequant tensor (first 10 elements):", dequantized[:10].tolist())
23+

python/example/requirements.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

python/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def build_extension(self, ext):
6262
name='pypiquant',
6363
author='Mario Sieg',
6464
author_email='mario@primeintellect.ai',
65-
packages=['piquant', 'piquant._compat'],
65+
packages=['piquant'],
6666
package_dir={'': 'src'}, # tell setuptools packages are under src/
6767
package_data={
6868
'piquant': ['libquant.so', 'libquant.dylib', 'libquant.dll'],

0 commit comments

Comments
 (0)