Optimize Qwen3.8 inference on Apple silicon
This commit is contained in:
@@ -50,6 +50,7 @@ enum {
|
||||
static id<MTLDevice> g_device;
|
||||
static id<MTLCommandQueue> g_queue;
|
||||
static id<MTLLibrary> g_library;
|
||||
static id<MTLLibrary> g_qwen_library;
|
||||
static id<MTLCommandBuffer> g_batch_cb;
|
||||
static id<MTLComputeCommandEncoder> g_batch_enc;
|
||||
static BOOL g_batch_encoder_concurrent;
|
||||
@@ -501,6 +502,7 @@ static int g_glm_stream_expert_addr_table_building;
|
||||
static uint64_t g_model_residency_count;
|
||||
static int g_model_residency_added_to_queue;
|
||||
static int g_glm_model_mode;
|
||||
static int g_qwen_model_mode;
|
||||
static int g_ssd_streaming_mode;
|
||||
static int g_glm_streaming_prefill_full_layer_runtime;
|
||||
static int g_metal4_runtime_available;
|
||||
@@ -1255,7 +1257,7 @@ static int ds4_gpu_scratch_needs_cpu_access(const char *label) {
|
||||
|
||||
static MTLResourceOptions ds4_gpu_model_resource_options(void) {
|
||||
MTLResourceOptions options = MTLResourceStorageModeShared;
|
||||
if (getenv("DS4_METAL_MODEL_UNTRACKED") != NULL) {
|
||||
if (g_qwen_model_mode || getenv("DS4_METAL_MODEL_UNTRACKED") != NULL) {
|
||||
options |= MTLResourceHazardTrackingModeUntracked;
|
||||
}
|
||||
return options;
|
||||
@@ -2238,7 +2240,10 @@ static id<MTLComputePipelineState> ds4_gpu_get_pipeline(
|
||||
|
||||
NSError *error = nil;
|
||||
NSString *name = [NSString stringWithUTF8String:function_name];
|
||||
id<MTLFunction> fn = [g_library newFunctionWithName:name];
|
||||
id<MTLLibrary> library = strncmp(function_name, "kernel_qwen_", 12) == 0
|
||||
? g_qwen_library
|
||||
: g_library;
|
||||
id<MTLFunction> fn = [library newFunctionWithName:name];
|
||||
if (!fn) {
|
||||
fprintf(stderr, "ds4: Metal %s function not found\n", function_name);
|
||||
return nil;
|
||||
@@ -2369,6 +2374,10 @@ static int ds4_gpu_mpp_available(void) {
|
||||
return g_metal4_tensor_api_enabled && !g_quality_mode;
|
||||
}
|
||||
|
||||
int ds4_gpu_metal4_tensor_api_enabled(void) {
|
||||
return g_metal4_tensor_api_enabled;
|
||||
}
|
||||
|
||||
/*
|
||||
* Retained Metal4 defaults live here instead of behind user-visible options.
|
||||
* The public runtime has one automatic accelerated path plus the global
|
||||
@@ -4228,6 +4237,10 @@ void ds4_gpu_set_glm_model(bool enabled) {
|
||||
g_glm_model_mode = enabled ? 1 : 0;
|
||||
}
|
||||
|
||||
void ds4_gpu_set_qwen_model(bool enabled) {
|
||||
g_qwen_model_mode = enabled ? 1 : 0;
|
||||
}
|
||||
|
||||
void ds4_gpu_set_ssd_streaming(bool enabled) {
|
||||
g_ssd_streaming_mode = enabled ? 1 : 0;
|
||||
ds4_gpu_stream_expert_cache_clear_all(1);
|
||||
@@ -4359,7 +4372,6 @@ static NSString *ds4_gpu_full_source(void) {
|
||||
@[@"DS4_METAL_GLM53_BF16_SOURCE", @"metal/glm53_bf16.metal"],
|
||||
@[@"DS4_METAL_GLM53_VISION_SOURCE", @"metal/glm53_vision.metal"],
|
||||
@[@"DS4_METAL_GLM53_KDA_SOURCE", @"metal/glm53_kda.metal"],
|
||||
@[@"DS4_METAL_QWEN38_SOURCE", @"metal/qwen38.metal"],
|
||||
@[@"DS4_METAL_MOE_SOURCE", @"metal/moe.metal"],
|
||||
@[@"DS4_METAL_DSV4_HC_SOURCE", @"metal/dsv4_hc.metal"],
|
||||
@[@"DS4_METAL_UNARY_SOURCE", @"metal/unary.metal"],
|
||||
@@ -4418,6 +4430,31 @@ static NSString *ds4_gpu_full_source(void) {
|
||||
return source;
|
||||
}
|
||||
|
||||
static NSString *ds4_gpu_qwen_source(void) {
|
||||
const char *override_path = getenv("DS4_METAL_QWEN38_SOURCE");
|
||||
NSMutableArray<NSString *> *paths = [NSMutableArray array];
|
||||
if (override_path && override_path[0]) {
|
||||
[paths addObject:[NSString stringWithUTF8String:override_path]];
|
||||
}
|
||||
[paths addObject:@"metal/qwen38.metal"];
|
||||
[paths addObject:@"./metal/qwen38.metal"];
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
for (NSString *path in paths) {
|
||||
if (![fm fileExistsAtPath:path]) continue;
|
||||
NSError *error = nil;
|
||||
NSString *source = [NSString stringWithContentsOfFile:path
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:&error];
|
||||
if (source) return source;
|
||||
fprintf(stderr, "ds4: failed to read Metal source %s: %s\n",
|
||||
[path UTF8String], [[error localizedDescription] UTF8String]);
|
||||
return nil;
|
||||
}
|
||||
fprintf(stderr,
|
||||
"ds4: Metal source metal/qwen38.metal not found (set DS4_METAL_QWEN38_SOURCE to override)\n");
|
||||
return nil;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int32_t ne00t;
|
||||
int32_t ne00;
|
||||
@@ -6507,6 +6544,37 @@ int ds4_gpu_init(void) {
|
||||
}
|
||||
g_library = library;
|
||||
|
||||
NSString *qwen_source = ds4_gpu_qwen_source();
|
||||
if (!qwen_source) {
|
||||
g_library = nil;
|
||||
g_queue = nil;
|
||||
g_device = nil;
|
||||
return 0;
|
||||
}
|
||||
MTLCompileOptions *qwen_options = [MTLCompileOptions new];
|
||||
if (@available(macOS 15.0, *)) {
|
||||
qwen_options.mathMode = MTLMathModeSafe;
|
||||
} else {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
qwen_options.fastMathEnabled = NO;
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
qwen_options.preprocessorMacros = macros;
|
||||
error = nil;
|
||||
id<MTLLibrary> qwen_library = [g_device newLibraryWithSource:qwen_source
|
||||
options:qwen_options
|
||||
error:&error];
|
||||
if (!qwen_library) {
|
||||
fprintf(stderr, "ds4: Qwen Metal shader compilation failed: %s\n",
|
||||
[[error localizedDescription] UTF8String]);
|
||||
g_library = nil;
|
||||
g_queue = nil;
|
||||
g_device = nil;
|
||||
return 0;
|
||||
}
|
||||
g_qwen_library = qwen_library;
|
||||
|
||||
id<MTLFunction> fn = [library newFunctionWithName:@"kernel_get_rows_f32"];
|
||||
if (!fn) {
|
||||
fprintf(stderr, "ds4: Metal kernel_get_rows_f32 function not found\n");
|
||||
@@ -8744,14 +8812,16 @@ void ds4_gpu_test_set_flags(uint32_t flags) {
|
||||
g_test_flags = flags;
|
||||
}
|
||||
|
||||
ds4_gpu_tensor *ds4_gpu_tensor_alloc(uint64_t bytes) {
|
||||
static ds4_gpu_tensor *ds4_gpu_tensor_alloc_with_options(
|
||||
uint64_t bytes,
|
||||
MTLResourceOptions options) {
|
||||
if (!g_initialized && !ds4_gpu_init()) return NULL;
|
||||
if (bytes == 0 || bytes > (uint64_t)NSUIntegerMax) return NULL;
|
||||
|
||||
@autoreleasepool {
|
||||
DS4MetalTensor *tensor = [DS4MetalTensor new];
|
||||
tensor.buffer = [g_device newBufferWithLength:(NSUInteger)bytes
|
||||
options:MTLResourceStorageModeShared];
|
||||
options:options];
|
||||
if (!tensor.buffer) {
|
||||
return NULL;
|
||||
}
|
||||
@@ -8783,6 +8853,16 @@ ds4_gpu_tensor *ds4_gpu_tensor_alloc(uint64_t bytes) {
|
||||
}
|
||||
}
|
||||
|
||||
ds4_gpu_tensor *ds4_gpu_tensor_alloc(uint64_t bytes) {
|
||||
return ds4_gpu_tensor_alloc_with_options(bytes, MTLResourceStorageModeShared);
|
||||
}
|
||||
|
||||
ds4_gpu_tensor *ds4_gpu_tensor_alloc_untracked(uint64_t bytes) {
|
||||
return ds4_gpu_tensor_alloc_with_options(
|
||||
bytes,
|
||||
MTLResourceStorageModeShared | MTLResourceHazardTrackingModeUntracked);
|
||||
}
|
||||
|
||||
ds4_gpu_tensor *ds4_gpu_tensor_alloc_managed(uint64_t bytes) {
|
||||
return ds4_gpu_tensor_alloc(bytes);
|
||||
}
|
||||
@@ -10528,6 +10608,7 @@ void ds4_gpu_cleanup(void) {
|
||||
g_model_buffer_cache = nil;
|
||||
g_transient_buffers = nil;
|
||||
g_pending_cbs = nil;
|
||||
g_qwen_library = nil;
|
||||
g_library = nil;
|
||||
g_queue = nil;
|
||||
g_device = nil;
|
||||
@@ -11744,13 +11825,14 @@ int ds4_gpu_qwen_dispatch(
|
||||
const ds4_gpu_tensor *a,
|
||||
const ds4_gpu_tensor *b,
|
||||
const ds4_gpu_tensor *c,
|
||||
const ds4_gpu_tensor *d,
|
||||
const ds4_gpu_qwen_weight_view *weights,
|
||||
uint32_t weight_count,
|
||||
const ds4_gpu_qwen_kernel_args *args,
|
||||
uint32_t grid_x,
|
||||
uint32_t grid_y) {
|
||||
if (!kernel || !out || !args || grid_x == 0 || grid_y == 0 ||
|
||||
weight_count > 3 || (weight_count != 0 && !weights)) {
|
||||
weight_count > 6 || (weight_count != 0 && !weights)) {
|
||||
return 0;
|
||||
}
|
||||
id<MTLComputePipelineState> pipeline = ds4_gpu_get_pipeline(kernel);
|
||||
@@ -11775,14 +11857,34 @@ int ds4_gpu_qwen_dispatch(
|
||||
[enc setBuffer:tensors[i].buffer offset:(NSUInteger)tensors[i].offset atIndex:1 + i];
|
||||
}
|
||||
}
|
||||
if (d) {
|
||||
const DS4MetalTensor *tensor = ds4_gpu_tensor_const_obj(d);
|
||||
[enc setBuffer:tensor.buffer offset:(NSUInteger)tensor.offset atIndex:8];
|
||||
}
|
||||
for (uint32_t i = 0; i < weight_count; i++) {
|
||||
uint64_t inner = 0;
|
||||
id<MTLBuffer> weight = ds4_gpu_wrap_model_exact_range(
|
||||
weights[i].map,
|
||||
weights[i].size,
|
||||
weights[i].offset,
|
||||
weights[i].bytes,
|
||||
&inner);
|
||||
id<MTLBuffer> weight = nil;
|
||||
if (weights[i].tensor) {
|
||||
const DS4MetalTensor *tensor = ds4_gpu_tensor_const_obj(weights[i].tensor);
|
||||
weight = tensor.buffer;
|
||||
inner = tensor.offset;
|
||||
} else if (!g_qwen_model_mode &&
|
||||
ds4_gpu_model_views_cover_range(weights[i].map,
|
||||
weights[i].size,
|
||||
weights[i].offset,
|
||||
weights[i].bytes)) {
|
||||
weight = ds4_gpu_wrap_model_range(weights[i].map,
|
||||
weights[i].size,
|
||||
weights[i].offset,
|
||||
weights[i].bytes,
|
||||
&inner);
|
||||
} else {
|
||||
weight = ds4_gpu_wrap_model_exact_range(weights[i].map,
|
||||
weights[i].size,
|
||||
weights[i].offset,
|
||||
weights[i].bytes,
|
||||
&inner);
|
||||
}
|
||||
if (!weight) {
|
||||
ds4_gpu_end_compute_encoder(cb, enc);
|
||||
if (owned) [cb commit];
|
||||
@@ -11791,16 +11893,34 @@ int ds4_gpu_qwen_dispatch(
|
||||
/* Metal resource offsets are four-byte aligned. Preserve an arbitrary
|
||||
* safetensors data offset for the Qwen kernels to decode explicitly. */
|
||||
const uint64_t aligned_inner = inner & ~3ull;
|
||||
bound_args.u[13 + i] = (uint32_t)(inner - aligned_inner);
|
||||
[enc setBuffer:weight offset:(NSUInteger)aligned_inner atIndex:5 + i];
|
||||
static const uint32_t offset_slots[6] = {13, 14, 15, 9, 6, 7};
|
||||
static const NSUInteger buffer_slots[6] = {5, 6, 7, 9, 10, 11};
|
||||
const uint32_t offset_slot = offset_slots[i];
|
||||
const NSUInteger buffer_slot = buffer_slots[i];
|
||||
bound_args.u[offset_slot] = (uint32_t)(inner - aligned_inner);
|
||||
[enc setBuffer:weight offset:(NSUInteger)aligned_inner atIndex:buffer_slot];
|
||||
}
|
||||
[enc setBytes:&bound_args length:sizeof(bound_args) atIndex:0];
|
||||
if (bound_args.u[10] != 0) {
|
||||
[enc setThreadgroupMemoryLength:bound_args.u[10] atIndex:0];
|
||||
}
|
||||
|
||||
const NSUInteger width = pipeline.threadExecutionWidth;
|
||||
const NSUInteger max_threads = pipeline.maxTotalThreadsPerThreadgroup;
|
||||
const NSUInteger threads = MIN(MAX(width, 1u), max_threads);
|
||||
[enc dispatchThreads:MTLSizeMake(grid_x, grid_y, 1)
|
||||
threadsPerThreadgroup:MTLSizeMake(threads, 1, 1)];
|
||||
const NSUInteger requested_threads = bound_args.u[12];
|
||||
if (requested_threads != 0) {
|
||||
if (requested_threads > max_threads || requested_threads % width != 0) {
|
||||
ds4_gpu_end_compute_encoder(cb, enc);
|
||||
if (owned) [cb commit];
|
||||
return 0;
|
||||
}
|
||||
[enc dispatchThreadgroups:MTLSizeMake(grid_x, grid_y, 1)
|
||||
threadsPerThreadgroup:MTLSizeMake(requested_threads, 1, 1)];
|
||||
} else {
|
||||
const NSUInteger threads = MIN(MAX(width, 1u), max_threads);
|
||||
[enc dispatchThreads:MTLSizeMake(grid_x, grid_y, 1)
|
||||
threadsPerThreadgroup:MTLSizeMake(threads, 1, 1)];
|
||||
}
|
||||
ds4_gpu_end_compute_encoder(cb, enc);
|
||||
return owned ? ds4_gpu_finish_command_buffer(cb, 1, kernel) : 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user