Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
811af52
load rht quantized weights as signed codes
eugenebokhan Jul 27, 2026
ac85838
feed a8w4 weights to mxu as int4b tensors
eugenebokhan Jul 27, 2026
3be7cad
split mxu fragment headers
eugenebokhan Jul 27, 2026
fb7d80c
use mpp matmul modes
eugenebokhan Jul 27, 2026
e1e6bfb
wip
eugenebokhan Jul 28, 2026
52c6a58
merge main
eugenebokhan Jul 28, 2026
d751cb5
adapt gemv for signed weights
eugenebokhan Jul 28, 2026
c2854ae
Merge branch 'main' into signed-quantized-weights
eugenebokhan Jul 28, 2026
2b5b0f9
add ActivationTransform kernel
eugenebokhan Jul 28, 2026
3ef7f3c
add matmul gemv/gemm path routing
eugenebokhan Jul 28, 2026
00c98be
migrate RHT to ActivationTransform
eugenebokhan Jul 28, 2026
ec9c484
Merge branch 'main' into signed-quantized-weights
eugenebokhan Jul 28, 2026
e8e6335
merge main
eugenebokhan Jul 29, 2026
6503dd6
make activation transform outputs optional
eugenebokhan Jul 29, 2026
67e150c
fix qlora output rht allocation size
eugenebokhan Jul 29, 2026
b1370a9
assert activation transform row width
eugenebokhan Jul 29, 2026
c50e9ad
drop native int8 shortcut from path select
eugenebokhan Jul 29, 2026
ec7ef4b
specialize signed weight codes
eugenebokhan Jul 29, 2026
9525e38
clean up mxu fragment headers
eugenebokhan Jul 29, 2026
7acfd8f
restore hadamard coverage as transform test
eugenebokhan Jul 29, 2026
124782d
add signed weight gemv benchmark
eugenebokhan Jul 29, 2026
b383122
apply metal formatting
eugenebokhan Jul 29, 2026
35326da
Merge branch 'main' into signed-quantized-weights
eugenebokhan Jul 29, 2026
f26111d
remove dead code and comments
eugenebokhan Jul 29, 2026
09168e7
simplify matmul and transform plumbing
eugenebokhan Jul 29, 2026
1c9dd16
consolidate activation transform tests
eugenebokhan Jul 29, 2026
17109ff
decouple signed codes from prepared a
eugenebokhan Jul 29, 2026
6c3fb9d
add signed weight gemm benchmark
eugenebokhan Jul 29, 2026
df5ad6e
rename gemm tiling selectors
eugenebokhan Jul 29, 2026
6b87248
restore fragment matmul inlining
eugenebokhan Jul 30, 2026
933996b
rename
eugenebokhan Jul 30, 2026
03bf585
keep sign mask literal in w4 dequantize
eugenebokhan Jul 30, 2026
3e54b85
bench bf16 paths with unsigned codes
eugenebokhan Jul 30, 2026
9ac1655
replace activation transform flags with enum
eugenebokhan Jul 30, 2026
c76bdc7
drop signed weight investigation benches
eugenebokhan Jul 30, 2026
f870170
drop redundant quant argument helper
eugenebokhan Jul 30, 2026
e1c1ad2
simplify gemm tiling selection
eugenebokhan Jul 30, 2026
c053e40
carry b prologue and signed codes in matmul shape
eugenebokhan Jul 30, 2026
d122007
fold cpu hadamard into activation transform
eugenebokhan Jul 30, 2026
258ad32
share quant b variant helper in tests
eugenebokhan Jul 30, 2026
28e8922
add in-place mode to activation transform
eugenebokhan Jul 30, 2026
b951468
guard mxu tiling lookup on full precision a
eugenebokhan Jul 30, 2026
70385d8
reuse metal quant runner in signed parity test
eugenebokhan Jul 30, 2026
e464090
keep bf16 bench paths comparable to main
eugenebokhan Jul 30, 2026
7eec560
Merge branch 'main' into signed-quantized-weights
eugenebokhan Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions crates/backend-uzu/BENCHMARKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ target is `--lib`.
|-----------------------------------------|-------------------------------------|
| `Metal/Kernel/Matmul/GEMM` | `Metal/Kernel/Matmul/GEMM` |
| `Metal/Kernel/Matmul/GEMM_MXU` | `Metal/Kernel/Matmul/GEMM_MXU` |
| `Metal/Kernel/A8W/w4`, `.../w8` | `Metal/Kernel/A8W` |
| `Metal/Kernel/UnifiedQuantizedGemm/...` | `Metal/Kernel/UnifiedQuantizedGemm` |
| `Metal/Kernel/Gemv/...` | `Metal/Kernel/Gemv` |
| `Metal/Kernel/Qwen3Layers/...` | `Metal/Kernel/Qwen3Layers` |
Expand Down Expand Up @@ -72,31 +73,39 @@ directory.
Run one benchmark group at a time to avoid the iOS watchdog killing the
app.

Set `IPHONEOS_DEPLOYMENT_TARGET` (value from `platforms.toml` `[envs]`)
for all iOS builds; without it the link step fails with undefined
symbols (e.g. `___chkstk_darwin`) because objects are built for a newer
SDK than the default deployment target.

Key flags:

- `-e CRITERION_HOME=target/criterion/a19` — on-device env var. Path is
- `-e CRITERION_HOME=criterion/a19` — on-device env var. Path is
relative to the app's cwd (`Documents/`), so this becomes
`Documents/target/criterion/a19/` on device.
- `--copy-back "Documents/target=$(pwd)/target"` — after the run,
`cargo-dinghy` pulls `Documents/target` from the device into your
repo's `target/`. `$(pwd)` is required (absolute DST) because the
`Documents/criterion/a19/` on device. Keep it directly under
`Documents/` — nested parents (e.g. `Documents/target/`) do not exist
on a fresh install and the pre-run sync cannot create them.
- `--sync-dirs "$(pwd)/target/criterion=Documents/criterion"` —
syncs the criterion tree between host and device before and after the
run, so results written on device land back in the repo's
`target/criterion/`. `$(pwd)` is required (absolute path) because the
cargo runner is launched with cwd set to the package dir, not the
workspace root.

```bash
DEVICE=<DEVICE_ID>

cargo dinghy \
IPHONEOS_DEPLOYMENT_TARGET=26.4 cargo dinghy \
-d "$DEVICE" \
-e CRITERION_HOME=target/criterion/a19 \
--copy-back "Documents/target=$(pwd)/target" \
-e CRITERION_HOME=criterion/a19 \
--sync-dirs "$(pwd)/target/criterion=Documents/criterion" \
bench -p backend-uzu --lib -- \
"Metal/Kernel/Matmul" \
--save-baseline matmul_baseline_a19
```

After the run completes you'll have
`target/criterion/a19/Metal/Kernel/Matmul/<GEMM|GEMM_MXU>/…/matmul_baseline_a19/`
On-device criterion sanitizes group path separators, so results land in
`target/criterion/a19/Metal_Kernel_Matmul_<GEMM|GEMM_MXU>/…/`
on the host, next to any `m2_max/` baselines.

## Viewing reports
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use bitflags::bitflags;

bitflags! {
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ActivationTransformOp: u32 {
const INPUT_RHT = 1 << 0;
const OUTPUT_RHT = 1 << 1;
const QUANTIZE = 1 << 2;
const GROUP_SUMS = 1 << 3;
}
Comment thread
CC-Yeh marked this conversation as resolved.
Outdated
}

impl ActivationTransformOp {
pub fn validate(self) -> Self {
assert!(
self.contains(Self::INPUT_RHT) ^ self.contains(Self::OUTPUT_RHT),
"exactly one of INPUT_RHT / OUTPUT_RHT is required, got {self:?}"
);
assert!(
!self.contains(Self::QUANTIZE) || self.contains(Self::INPUT_RHT),
"QUANTIZE requires INPUT_RHT, got {self:?}"
);
assert!(
!self.contains(Self::GROUP_SUMS) || self.contains(Self::QUANTIZE),
"GROUP_SUMS requires QUANTIZE, got {self:?}"
);
self
}
}
1 change: 1 addition & 0 deletions crates/backend-uzu/src/backends/common/gpu_types/matmul.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ pub struct GemmParams {
pub aligned_inner_iterations: u32,
pub use_morton: bool,
pub ab_scale: f32,
pub weight_codes_sign_flip_mask: u32,
}
2 changes: 2 additions & 0 deletions crates/backend-uzu/src/backends/common/gpu_types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! These `#[repr(C)]` structs are the source of truth. The build system uses
//! cbindgen to generate C headers for Metal shaders.

pub mod activation_transform;
pub mod activation_type;
pub mod argmax;
pub mod attention;
Expand All @@ -16,6 +17,7 @@ pub mod ring;
pub mod trie;
pub mod weaver;

pub use activation_transform::*;
pub use activation_type::*;
pub use argmax::*;
pub use attention::*;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ impl QuantizationMode {
QuantizationMode::U8 => DataType::U8,
}
}

pub fn weight_codes_sign_flip_mask(&self) -> Option<u8> {
match self {
QuantizationMode::U4 => Some(0x88),
QuantizationMode::U8 => Some(0x80),
QuantizationMode::I8 => None,
}
}
}

impl From<QuantizationMode> for DataType {
Expand Down
121 changes: 121 additions & 0 deletions crates/backend-uzu/src/backends/common/kernel/activation_transform.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use crate::backends::common::{
Allocation, Backend, Encoder, Kernels,
gpu_types::{ActivationTransformOp, HADAMARD_TRANSFORM_BLOCK_SIZE},
kernel::ActivationTransformKernel,
};

pub struct ActivationTransform<B: Backend> {
kernel: <B::Kernels as Kernels>::ActivationTransformKernel,
ops: ActivationTransformOp,
group_size: u32,
}

impl<B: Backend> ActivationTransform<B> {
pub fn new(
context: &B::Context,
data_type: crate::data_type::DataType,
ops: ActivationTransformOp,
group_size: u32,
) -> Result<Self, B::Error> {
let ops = ops.validate();
let kernel = <B::Kernels as Kernels>::ActivationTransformKernel::new(context, data_type, ops)?;
Ok(Self {
kernel,
ops,
group_size,
})
}

pub fn input_rht(
context: &B::Context,
data_type: crate::data_type::DataType,
) -> Result<Self, B::Error> {
Self::new(context, data_type, ActivationTransformOp::INPUT_RHT, HADAMARD_TRANSFORM_BLOCK_SIZE as u32)
}

pub fn output_rht(
context: &B::Context,
data_type: crate::data_type::DataType,
) -> Result<Self, B::Error> {
Self::new(context, data_type, ActivationTransformOp::OUTPUT_RHT, HADAMARD_TRANSFORM_BLOCK_SIZE as u32)
}

pub fn quantize(
context: &B::Context,
data_type: crate::data_type::DataType,
group_size: u32,
emit_group_sums: bool,
) -> Result<Self, B::Error> {
let ops = if emit_group_sums {
ActivationTransformOp::INPUT_RHT | ActivationTransformOp::QUANTIZE | ActivationTransformOp::GROUP_SUMS
} else {
ActivationTransformOp::INPUT_RHT | ActivationTransformOp::QUANTIZE
};
Self::new(context, data_type, ops, group_size)
}

/// FP Hadamard (input- or output-order depending on construction).
/// `input` and `output` must be distinct buffers. The two scratch buffers are
/// placeholders for the quantized outputs this mode does not write.
pub fn encode_fp(
&self,
input: &Allocation<B>,
output: &mut Allocation<B>,
q_scratch: &mut Allocation<B>,
scales_scratch: &mut Allocation<B>,
rht_factors: &Allocation<B>,
element_count: u32,
batch_size: u32,
encoder: &mut Encoder<B>,
) {
assert!(!self.ops.contains(ActivationTransformOp::QUANTIZE));
self.kernel.encode(
input,
output,
q_scratch,
scales_scratch,
None::<&mut Allocation<B>>,
rht_factors,
batch_size,
element_count,
self.group_size,
encoder,
);
}

/// Input RHT + symmetric int8 quantization.
pub fn encode_quantize(
&self,
input: &Allocation<B>,
fp_scratch: &mut Allocation<B>,
q_out: &mut Allocation<B>,
scales_out: &mut Allocation<B>,
group_sums_out: Option<&mut Allocation<B>>,
rht_factors: &Allocation<B>,
batch_size: u32,
element_count: u32,
encoder: &mut Encoder<B>,
) {
assert!(self.ops.contains(ActivationTransformOp::QUANTIZE));
self.kernel.encode(
input,
fp_scratch,
q_out,
scales_out,
group_sums_out,
rht_factors,
batch_size,
element_count,
self.group_size,
encoder,
);
}

pub fn ops(&self) -> ActivationTransformOp {
self.ops
}

pub fn emit_group_sums(&self) -> bool {
self.ops.contains(ActivationTransformOp::GROUP_SUMS)
}
}
18 changes: 17 additions & 1 deletion crates/backend-uzu/src/backends/common/kernel/matmul/kernel.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
use crate::{
backends::common::{Backend, BufferArg, Encoder, Kernels, kernel::matmul::arguments::MatmulArguments},
backends::common::{
Backend, BufferArg, Encoder, Kernels,
gpu_types::gemm::GemmBPrologueKind,
kernel::matmul::{
arguments::MatmulArguments,
routing::{MatmulPath, MatmulShape},
},
},
data_type::DataType,
};

Expand All @@ -18,4 +25,13 @@ pub trait MatmulKernel: Sized + Send + Sync {
arguments: MatmulArguments<'a, 'b, 'd, Self::Backend, TB>,
encoder: &mut Encoder<Self::Backend>,
) -> Result<(), <Self::Backend as Backend>::Error>;

fn select_path(
&self,
_shape: &MatmulShape,
_b_prologue: GemmBPrologueKind,
_context: &<Self::Backend as Backend>::Context,
) -> MatmulPath {
MatmulPath::Gemm
}
}
23 changes: 23 additions & 0 deletions crates/backend-uzu/src/backends/common/kernel/matmul/matmul_b.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@ pub enum MatmulB<'a, B: Backend, TB: BufferArg<'a, B> = &'a Allocation<B>> {
biases: &'a Allocation<B>,
mode: QuantizationMode,
group_size: u32,
signed_codes: bool,
},
ScaleZeroPointDequant {
b: &'a Allocation<B>,
scales: &'a Allocation<B>,
zero_points: &'a Allocation<B>,
mode: QuantizationMode,
group_size: u32,
signed_codes: bool,
},
ScaleSymmetricDequant {
b: &'a Allocation<B>,
scales: &'a Allocation<B>,
mode: QuantizationMode,
group_size: u32,
signed_codes: bool,
},
}

Expand Down Expand Up @@ -89,4 +92,24 @@ impl<'a, B: Backend, TB: BufferArg<'a, B>> MatmulB<'a, B, TB> {
} => Some(*group_size),
}
}

pub fn signed_codes(&self) -> bool {
match self {
Self::FullPrecision {
..
} => false,
Self::ScaleBiasDequant {
signed_codes,
..
}
| Self::ScaleZeroPointDequant {
signed_codes,
..
}
| Self::ScaleSymmetricDequant {
signed_codes,
..
} => *signed_codes,
}
}
}
2 changes: 2 additions & 0 deletions crates/backend-uzu/src/backends/common/kernel/matmul/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ mod error;
mod kernel;
mod matmul_a;
mod matmul_b;
pub mod routing;

pub use arguments::MatmulArguments;
pub use d_ops::MatmulDOps;
pub use error::MatmulError;
pub use kernel::MatmulKernel;
pub use matmul_a::MatmulA;
pub use matmul_b::MatmulB;
pub use routing::{MatmulPath, MatmulShape};
21 changes: 21 additions & 0 deletions crates/backend-uzu/src/backends/common/kernel/matmul/routing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use crate::backends::common::gpu_types::gemm::GemmDTransform;

#[derive(Debug, Clone, Copy)]
pub struct MatmulShape {
pub m: u32,
pub n: u32,
pub k: u32,
pub b_transpose: bool,
pub b_leading_dimension: Option<u32>,
pub is_quant: bool,
pub b_bits: Option<u32>,
pub b_group_size: Option<u32>,
pub gathered: bool,
pub d_transform: GemmDTransform,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MatmulPath {
Gemv,
Gemm,
}
3 changes: 3 additions & 0 deletions crates/backend-uzu/src/backends/common/kernel/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use crate::backends::common::Backend;

pub mod activation_transform;
pub mod attention_gemm;
pub mod delta_net_chunked_prefill;
pub mod delta_net_tree_verify;
pub mod matmul;
pub mod radix_top_k_small;

pub use activation_transform::ActivationTransform;

include!(concat!(env!("OUT_DIR"), "/traits.rs"));

pub trait Kernels: Sized {
Expand Down
Loading
Loading