Skip to content

Commit bfc3f4e

Browse files
committed
First attempt
1 parent 7511e51 commit bfc3f4e

5 files changed

Lines changed: 339 additions & 6 deletions

File tree

candle-core/src/metal_backend/device.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::collections::HashMap;
55
use std::path::Path;
66
use std::sync::{Arc, Mutex, RwLock};
77

8-
use super::MetalError;
8+
use super::{current_pool, register_pool_allocation, MetalError};
99

1010
// iOS and macOS have different storage modes for shared buffers.
1111
// due to the GPU/CPU management differences.
@@ -242,8 +242,16 @@ impl MetalDevice {
242242
dtype: DType,
243243
name: &str,
244244
) -> Result<Arc<Buffer>> {
245-
let size = (element_count * dtype.size_in_bytes()) as NSUInteger;
246-
self.allocate_buffer(size, MTLResourceOptions::StorageModePrivate, name)
245+
let size_bytes = element_count * dtype.size_in_bytes();
246+
if let Some(pool) = current_pool() {
247+
let allocation = pool.allocate(size_bytes)?;
248+
let buffer = Arc::clone(allocation.buffer());
249+
register_pool_allocation(&buffer, allocation);
250+
Ok(buffer)
251+
} else {
252+
let size = size_bytes as NSUInteger;
253+
self.allocate_buffer(size, MTLResourceOptions::StorageModePrivate, name)
254+
}
247255
}
248256

249257
pub fn new_buffer_private(
@@ -252,8 +260,7 @@ impl MetalDevice {
252260
dtype: DType,
253261
name: &str,
254262
) -> Result<Arc<Buffer>> {
255-
let size = (element_count * dtype.size_in_bytes()) as NSUInteger;
256-
self.allocate_buffer(size, metal::MTLResourceOptions::StorageModePrivate, name)
263+
self.new_buffer(element_count, dtype, name)
257264
}
258265

259266
/// Creates a new buffer (not necessarily zeroed).

candle-core/src/metal_backend/mod.rs

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,58 @@ use crate::op::{BinaryOpT, CmpOp, ReduceOp, UnaryOpT};
66
use crate::{CpuStorage, CpuStorageRef, DType, Error, Layout, Result, Shape};
77
use candle_metal_kernels::{BufferOffset, CallConvTranspose2dCfg, Kernels};
88
use metal::{Buffer, NSUInteger};
9+
use std::cell::RefCell;
910
use std::collections::HashMap;
1011
use std::ffi::c_void;
11-
use std::sync::{Arc, Mutex, PoisonError, RwLock, TryLockError};
12+
use std::sync::{Arc, Mutex, OnceLock, PoisonError, RwLock, TryLockError};
1213

1314
mod device;
15+
mod pool;
1416
pub use device::{DeviceId, MetalDevice, SHARED_BUFFER_STORAGE_MODE};
17+
pub use pool::{MetalPoolAllocation, MetalTensorPool};
18+
19+
thread_local! {
20+
static POOL_STACK: RefCell<Vec<Option<Arc<MetalTensorPool>>>> = RefCell::new(Vec::new());
21+
}
22+
23+
pub(crate) struct PoolContextGuard;
24+
25+
impl Drop for PoolContextGuard {
26+
fn drop(&mut self) {
27+
POOL_STACK.with(|stack| {
28+
stack.borrow_mut().pop();
29+
});
30+
}
31+
}
32+
33+
pub(crate) fn push_pool_context(pool: Option<Arc<MetalTensorPool>>) -> PoolContextGuard {
34+
POOL_STACK.with(|stack| stack.borrow_mut().push(pool));
35+
PoolContextGuard
36+
}
37+
38+
fn current_pool() -> Option<Arc<MetalTensorPool>> {
39+
POOL_STACK.with(|stack| stack.borrow().last().cloned().flatten())
40+
}
41+
42+
fn pool_registry() -> &'static Mutex<HashMap<usize, Arc<MetalPoolAllocation>>> {
43+
static REGISTRY: OnceLock<Mutex<HashMap<usize, Arc<MetalPoolAllocation>>>> = OnceLock::new();
44+
REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
45+
}
46+
47+
fn register_pool_allocation(buffer: &Arc<Buffer>, allocation: Arc<MetalPoolAllocation>) {
48+
let ptr = Arc::as_ptr(buffer) as usize;
49+
if let Ok(mut registry) = pool_registry().lock() {
50+
registry.insert(ptr, allocation);
51+
}
52+
}
53+
54+
fn take_pool_allocation(buffer: &Arc<Buffer>) -> Option<Arc<MetalPoolAllocation>> {
55+
let ptr = Arc::as_ptr(buffer) as usize;
56+
pool_registry()
57+
.lock()
58+
.ok()
59+
.and_then(|mut registry| registry.remove(&ptr))
60+
}
1561

1662
pub fn buffer_o<'a>(buffer: &'a Buffer, l: &Layout, dtype: DType) -> BufferOffset<'a> {
1763
BufferOffset {
@@ -77,6 +123,7 @@ pub struct MetalStorage {
77123
count: usize,
78124
/// The dtype is kept since buffers are untyped.
79125
dtype: DType,
126+
pool_allocation: Option<Arc<MetalPoolAllocation>>,
80127
}
81128

82129
impl BackendStorage for MetalStorage {
@@ -113,6 +160,7 @@ impl BackendStorage for MetalStorage {
113160
}
114161

115162
fn affine(&self, layout: &Layout, mul: f64, add: f64) -> Result<Self> {
163+
let _guard = Self::pool_guard(&[self])?;
116164
let device = self.device().clone();
117165

118166
let shape = layout.shape();
@@ -168,6 +216,7 @@ impl BackendStorage for MetalStorage {
168216
}
169217

170218
fn powf(&self, layout: &Layout, pow: f64) -> Result<Self> {
219+
let _guard = Self::pool_guard(&[self])?;
171220
let device = self.device().clone();
172221

173222
let shape = layout.shape();
@@ -219,6 +268,7 @@ impl BackendStorage for MetalStorage {
219268
}
220269

221270
fn elu(&self, layout: &Layout, alpha: f64) -> Result<Self> {
271+
let _guard = Self::pool_guard(&[self])?;
222272
let device = self.device().clone();
223273

224274
let shape = layout.shape();
@@ -270,6 +320,7 @@ impl BackendStorage for MetalStorage {
270320
}
271321

272322
fn reduce_op(&self, op: ReduceOp, layout: &Layout, sum_dims: &[usize]) -> Result<Self> {
323+
let _guard = Self::pool_guard(&[self])?;
273324
let device = self.device.clone();
274325

275326
let src_stride = layout.stride();
@@ -408,6 +459,7 @@ impl BackendStorage for MetalStorage {
408459
}
409460

410461
fn cmp(&self, op: CmpOp, rhs: &Self, lhs_l: &Layout, rhs_l: &Layout) -> Result<Self> {
462+
let _guard = Self::pool_guard(&[self, rhs])?;
411463
let name = match op {
412464
CmpOp::Eq => "eq",
413465
CmpOp::Ne => "ne",
@@ -527,6 +579,7 @@ impl BackendStorage for MetalStorage {
527579
}
528580

529581
fn to_dtype(&self, layout: &Layout, dtype: DType) -> Result<Self> {
582+
let _guard = Self::pool_guard(&[self])?;
530583
let device = self.device();
531584
let shape = layout.shape();
532585
let el_count = shape.elem_count();
@@ -644,6 +697,7 @@ impl BackendStorage for MetalStorage {
644697
}
645698

646699
fn unary_impl<B: UnaryOpT>(&self, layout: &Layout) -> Result<Self> {
700+
let _guard = Self::pool_guard(&[self])?;
647701
let device = self.device();
648702
let dtype = self.dtype;
649703
let shape = layout.shape();
@@ -904,6 +958,7 @@ impl BackendStorage for MetalStorage {
904958
f: &Self,
905959
f_l: &Layout,
906960
) -> Result<Self> {
961+
let _guard = Self::pool_guard(&[self, t, f])?;
907962
let device = self.device.clone();
908963
let shape = t_l.shape();
909964
let dims = shape.dims();
@@ -956,6 +1011,7 @@ impl BackendStorage for MetalStorage {
9561011
kernel_l: &Layout,
9571012
params: &ParamsConv1D,
9581013
) -> Result<Self> {
1014+
let _guard = Self::pool_guard(&[self, kernel])?;
9591015
let device = self.device().clone();
9601016
let shape = layout.shape();
9611017
let dims = shape.dims();
@@ -993,6 +1049,7 @@ impl BackendStorage for MetalStorage {
9931049
device,
9941050
count: dst_el,
9951051
dtype: self.dtype,
1052+
pool_allocation: None,
9961053
};
9971054
let l_out = params.l_out();
9981055
let b = params.b_size;
@@ -1027,6 +1084,7 @@ impl BackendStorage for MetalStorage {
10271084
k_layout: &Layout,
10281085
params: &ParamsConvTranspose1D,
10291086
) -> Result<Self> {
1087+
let _guard = Self::pool_guard(&[self, k])?;
10301088
const USE_COL2IM_CONV1D_TR: bool = true;
10311089

10321090
let can_use_col2im = k_layout.is_contiguous()
@@ -1138,6 +1196,7 @@ impl BackendStorage for MetalStorage {
11381196
kernel_l: &Layout,
11391197
params: &ParamsConv2D,
11401198
) -> Result<Self> {
1199+
let _guard = Self::pool_guard(&[self, kernel])?;
11411200
let device = self.device().clone();
11421201
let shape = layout.shape();
11431202
let dims = shape.dims();
@@ -1183,6 +1242,7 @@ impl BackendStorage for MetalStorage {
11831242
device,
11841243
count: dst_el,
11851244
dtype: self.dtype,
1245+
pool_allocation: None,
11861246
};
11871247
let h_out = params.out_h();
11881248
let w_out = params.out_w();
@@ -1220,6 +1280,7 @@ impl BackendStorage for MetalStorage {
12201280
kernel_l: &Layout,
12211281
params: &ParamsConvTranspose2D,
12221282
) -> Result<Self> {
1283+
let _guard = Self::pool_guard(&[self, kernel])?;
12231284
// Kernel shape: (c_in_k, c_out, h_k, w_k)
12241285
// Input shape: (b_size, c_in, h_in, w_in)
12251286
let (out_w, out_h) = (params.out_w(), params.out_h());
@@ -1283,6 +1344,7 @@ impl BackendStorage for MetalStorage {
12831344
(w_k, h_k): (usize, usize),
12841345
(w_stride, h_stride): (usize, usize),
12851346
) -> Result<Self> {
1347+
let _guard = Self::pool_guard(&[self])?;
12861348
let shape = inp_l.shape();
12871349
let (b_size, channels, width, height) = shape.dims4()?;
12881350
let strides = inp_l.stride();
@@ -1325,6 +1387,7 @@ impl BackendStorage for MetalStorage {
13251387
(w_k, h_k): (usize, usize),
13261388
(w_stride, h_stride): (usize, usize),
13271389
) -> Result<Self> {
1390+
let _guard = Self::pool_guard(&[self])?;
13281391
let shape = inp_l.shape();
13291392
let (b_size, channels, width, height) = shape.dims4()?;
13301393
let strides = inp_l.stride();
@@ -1366,6 +1429,7 @@ impl BackendStorage for MetalStorage {
13661429
}
13671430

13681431
fn upsample_nearest2d(&self, inp_l: &Layout, out_w: usize, out_h: usize) -> Result<Self> {
1432+
let _guard = Self::pool_guard(&[self])?;
13691433
// let inp = &inp.slice(inp_l.start_offset()..);
13701434
let shape = inp_l.shape();
13711435
let dims = shape.dims();
@@ -1405,6 +1469,7 @@ impl BackendStorage for MetalStorage {
14051469
}
14061470

14071471
fn gather(&self, src_l: &Layout, ids: &Self, ids_l: &Layout, dim: usize) -> Result<Self> {
1472+
let _guard = Self::pool_guard(&[self, ids])?;
14081473
if !ids_l.is_contiguous() {
14091474
return Err(crate::Error::RequiresContiguous { op: "gather" }.bt());
14101475
};
@@ -1544,6 +1609,7 @@ impl BackendStorage for MetalStorage {
15441609
}
15451610

15461611
fn index_select(&self, ids: &Self, src_l: &Layout, ids_l: &Layout, dim: usize) -> Result<Self> {
1612+
let _guard = Self::pool_guard(&[self, ids])?;
15471613
if !ids_l.is_contiguous() {
15481614
crate::bail!("Metal index_select requires contiguous ids")
15491615
}
@@ -1671,6 +1737,7 @@ impl BackendStorage for MetalStorage {
16711737
lhs_l: &Layout,
16721738
rhs_l: &Layout,
16731739
) -> Result<Self> {
1740+
let _guard = Self::pool_guard(&[self, rhs])?;
16741741
let buffer = self.device.new_buffer(b * m * n, self.dtype, "matmul")?;
16751742
let command_buffer = self.device.command_buffer()?;
16761743
command_buffer.set_label("matmul");
@@ -1819,14 +1886,43 @@ impl BackendStorage for MetalStorage {
18191886

18201887
impl MetalStorage {
18211888
pub fn new(buffer: Arc<Buffer>, device: MetalDevice, count: usize, dtype: DType) -> Self {
1889+
let pool_allocation = take_pool_allocation(&buffer);
18221890
Self {
18231891
buffer,
18241892
device,
18251893
count,
18261894
dtype,
1895+
pool_allocation,
18271896
}
18281897
}
18291898

1899+
pub fn pool(&self) -> Option<Arc<MetalTensorPool>> {
1900+
self.pool_allocation
1901+
.as_ref()
1902+
.map(|allocation| allocation.pool())
1903+
}
1904+
1905+
fn determine_pool(storages: &[&Self]) -> Result<Option<Arc<MetalTensorPool>>> {
1906+
let mut pool: Option<Arc<MetalTensorPool>> = None;
1907+
for storage in storages {
1908+
if let Some(candidate) = storage.pool() {
1909+
if let Some(existing) = &pool {
1910+
if !Arc::ptr_eq(existing, &candidate) {
1911+
crate::bail!("Cannot operate on tensors from different pools");
1912+
}
1913+
} else {
1914+
pool = Some(candidate);
1915+
}
1916+
}
1917+
}
1918+
Ok(pool)
1919+
}
1920+
1921+
pub(crate) fn pool_guard(storages: &[&Self]) -> Result<PoolContextGuard> {
1922+
let pool = Self::determine_pool(storages)?;
1923+
Ok(push_pool_context(pool))
1924+
}
1925+
18301926
pub fn buffer(&self) -> &Buffer {
18311927
&self.buffer
18321928
}
@@ -1838,6 +1934,7 @@ impl MetalStorage {
18381934
lhs_l: &Layout,
18391935
rhs_l: &Layout,
18401936
) -> Result<Self> {
1937+
let _guard = Self::pool_guard(&[self, rhs])?;
18411938
let device = self.device();
18421939
let shape = lhs_l.shape();
18431940
let el_count = shape.elem_count();

0 commit comments

Comments
 (0)