@@ -756,10 +756,12 @@ int tq_metal_moe_available(void) {
756756}
757757
758758/* *
759- * Fused MoE forward: 3 dispatches for all active experts.
759+ * Hybrid GPU/CPU MoE forward: GPU does gate+up+SwiGLU (Phases 1+2),
760+ * CPU handles down projection (Phase 3 IQ2_S shader hangs on Metal).
760761 *
761762 * @param input Input hidden state [hidden_dim] (CPU pointer, will be uploaded)
762- * @param output Output hidden state [hidden_dim] (CPU pointer, will be downloaded)
763+ * @param output Output hidden state [hidden_dim] (unused in hybrid mode)
764+ * @param hb_output SwiGLU'd activations [num_active * expert_dim] (filled by GPU)
763765 * @param weight_base Base pointer for all expert weights (mmap'd GGUF data)
764766 * @param weight_size Total size of weight region (for zero-copy buffer)
765767 * @param gate_offsets Byte offsets from weight_base to each active expert's gate weights [num_active]
@@ -775,11 +777,13 @@ int tq_metal_moe_available(void) {
775777 * @param gate_types_in Per-expert gate quant types [num_active] (NULL = use weight_type for all)
776778 * @param up_types_in Per-expert up quant types [num_active] (NULL = use weight_type for all)
777779 * @param down_types_in Per-expert down quant types [num_active] (NULL = use weight_type for all)
778- * @return 0 on success, -1 on failure
780+ * @return 1 on partial success (hb_output filled, caller does down+accum on CPU),
781+ * -1 on failure
779782 */
780783int tq_metal_moe_forward (
781784 const float * input,
782785 float * output,
786+ float * hb_output,
783787 const void * weight_base,
784788 size_t weight_size,
785789 const uint64_t * gate_offsets,
@@ -812,11 +816,21 @@ int tq_metal_moe_forward(
812816 params.num_experts_total = num_experts_total;
813817 params.gate_type = (uint32_t )weight_type;
814818
815- /* Default block geometry uses IQ2_XXS (66 bytes per 256 elements) */
819+ /* Block geometry: blocks_per_row is type-independent (always 256 elements/block).
820+ * row_bytes_gate/down use IQ2_XXS (66) as default; shader overrides per-expert
821+ * for IQ2_S (82) via the gate_types/down_types arrays. */
816822 params.blocks_per_row_gate = hidden_dim / 256 ;
817- params.row_bytes_gate = params.blocks_per_row_gate * 66 ;
818823 params.blocks_per_row_down = expert_dim / 256 ;
819- params.row_bytes_down = params.blocks_per_row_down * 66 ;
824+
825+ /* Determine default row bytes from first expert's type */
826+ int first_gate_type = gate_types_in ? gate_types_in[0 ] : weight_type;
827+ int first_down_type = down_types_in ? down_types_in[0 ] : weight_type;
828+ int gate_blk_bytes = (first_gate_type == 22 ) ? 82 : 66 ; /* IQ2_S=82, IQ2_XXS=66 */
829+ int down_blk_bytes = (first_down_type == 22 ) ? 82 : 66 ;
830+ params.row_bytes_gate = params.blocks_per_row_gate * gate_blk_bytes;
831+ params.row_bytes_down = params.blocks_per_row_down * down_blk_bytes;
832+ params.gate_row_bytes = params.row_bytes_gate ;
833+ params.down_row_bytes = params.row_bytes_down ;
820834
821835 for (int k = 0 ; k < num_active; k++) {
822836 params.active_experts [k] = active_expert_ids[k];
@@ -850,11 +864,8 @@ int tq_metal_moe_forward(
850864 options: MTLResourceStorageModeShared ];
851865 if (!gate_buf || !up_buf) return -1 ;
852866
853- /* --- Create output buffer --- */
854- size_t output_bytes = (size_t )hidden_dim * sizeof (float );
855- id <MTLBuffer > output_buf = [tq_mtl_device newBufferWithLength: output_bytes
856- options: MTLResourceStorageModeShared ];
857- if (!output_buf) return -1 ;
867+ /* output_buf removed: hybrid mode returns SwiGLU results via hb_output,
868+ * down projection + accumulate happens on CPU in tq_moe.c */
858869
859870 /* --- Create params buffer --- */
860871 id <MTLBuffer > params_buf = [tq_mtl_device newBufferWithBytes: ¶ms
@@ -866,16 +877,10 @@ int tq_metal_moe_forward(
866877 id <MTLCommandBuffer > cmdBuf = [tq_mtl_queue commandBuffer ];
867878 if (!cmdBuf) return -1 ;
868879
869- /* Shared memory sizes:
870- * Phase 1 (gate_up): hidden_dim floats for input + 8 floats for SIMD sums
871- * Phase 3 (down): expert_dim floats for hb + 8 floats for SIMD sums */
880+ /* Shared memory for Phase 1 (gate_up): hidden_dim floats for input + 8 for SIMD sums */
872881 NSUInteger shared_phase1 = ((NSUInteger )hidden_dim + 8 ) * sizeof (float );
873- NSUInteger shared_phase3 = ((NSUInteger )expert_dim + 8 ) * sizeof (float );
874882 NSUInteger max_shared = [tq_mtl_device maxThreadgroupMemoryLength ];
875-
876- /* Cap shared memory to device limit */
877883 if (shared_phase1 > max_shared) shared_phase1 = max_shared;
878- if (shared_phase3 > max_shared) shared_phase3 = max_shared;
879884
880885 /* ======== Phase 1: gate + up (fused) ======== */
881886 {
@@ -908,6 +913,38 @@ int tq_metal_moe_forward(
908913 }
909914 NSLog (@" TurboQuant MoE: Phase 1 (gate+up) completed OK" );
910915
916+ #ifdef TQ_MOE_DEBUG_VALIDATE
917+ /* === Debug: compare GPU gate output for expert 0 vs CPU tq_matmul_gguf === */
918+ {
919+ /* tq_matmul_gguf declared in tq_gguf.h (already included) */
920+ float * gpu_gate = (float *)[gate_buf contents ];
921+ float * cpu_gate = (float *)malloc ((size_t )expert_dim * sizeof (float ));
922+ if (cpu_gate) {
923+ /* CPU matmul for expert 0's gate weights */
924+ const uint8_t * gate_w = (const uint8_t *)weight_base + gate_offsets[0 ];
925+ tq_ggml_dtype gt0 = gate_types_in ? (tq_ggml_dtype)gate_types_in[0 ]
926+ : (tq_ggml_dtype)weight_type;
927+ tq_matmul_gguf (cpu_gate, input, gate_w, gt0, expert_dim, hidden_dim);
928+
929+ /* Compare first 8 and last 8 values */
930+ NSLog (@" TurboQuant MoE DEBUG: gate expert 0 comparison (first 8):" );
931+ float max_err = 0 .0f ;
932+ for (int i = 0 ; i < expert_dim; i++) {
933+ float err = fabsf (gpu_gate[i] - cpu_gate[i]);
934+ if (err > max_err) max_err = err;
935+ if (i < 8 || i >= expert_dim - 4 ) {
936+ NSLog (@" [%d ] GPU=%.6f CPU=%.6f err=%.6f " , i, gpu_gate[i], cpu_gate[i], err);
937+ }
938+ }
939+ NSLog (@" TurboQuant MoE DEBUG: gate max_err=%.6f across %d elements" , max_err, expert_dim);
940+ if (max_err > 0 .01f ) {
941+ NSLog (@" TurboQuant MoE DEBUG: *** MISMATCH DETECTED *** — weight offset or decoding bug" );
942+ }
943+ free (cpu_gate);
944+ }
945+ }
946+ #endif /* TQ_MOE_DEBUG_VALIDATE */
947+
911948 /* --- New command buffer for Phase 2 --- */
912949 cmdBuf = [tq_mtl_queue commandBuffer ];
913950 if (!cmdBuf) return -1 ;
@@ -941,42 +978,13 @@ int tq_metal_moe_forward(
941978 }
942979 NSLog (@" TurboQuant MoE: Phase 2 (SwiGLU) completed OK" );
943980
944- /* --- New command buffer for Phase 3 --- */
945- cmdBuf = [tq_mtl_queue commandBuffer ];
946- if (!cmdBuf) return -1 ;
947-
948- /* ======== Phase 3: down projection + weighted accumulation ======== */
949- {
950- id <MTLComputeCommandEncoder > enc = [cmdBuf computeCommandEncoder ];
951- if (!enc) return -1 ;
952-
953- [enc setComputePipelineState: tq_pipe_moe_down_accum];
954- [enc setBuffer: weight_buf offset: 0 atIndex: 0 ];
955- [enc setBuffer: gate_buf offset: 0 atIndex: 1 ]; /* hb_all (post-SwiGLU) */
956- [enc setBuffer: output_buf offset: 0 atIndex: 2 ];
957- [enc setBuffer: params_buf offset: 0 atIndex: 3 ];
958- [enc setThreadgroupMemoryLength: shared_phase3 atIndex: 0 ];
959-
960- /* One threadgroup per output row */
961- NSUInteger n_tgs = (NSUInteger )hidden_dim;
962- MTLSize gridSize = MTLSizeMake (n_tgs, 1 , 1 );
963- MTLSize tgSize = MTLSizeMake (TQ_MATMUL_TG_SIZE , 1 , 1 );
964- [enc dispatchThreadgroups: gridSize threadsPerThreadgroup: tgSize];
965- [enc endEncoding ];
966- }
967-
968- /* --- Phase 3: commit and wait --- */
969- [cmdBuf commit ];
970- [cmdBuf waitUntilCompleted ];
971-
972- if (cmdBuf.status == MTLCommandBufferStatusError ) {
973- NSLog (@" TurboQuant MoE: Phase 3 (down+accum) FAILED: %@ " , cmdBuf.error );
974- return -1 ;
975- }
976- NSLog (@" TurboQuant MoE: Phase 3 (down+accum) completed OK" );
977-
978- memcpy (output, [output_buf contents ], output_bytes);
979- return 0 ;
981+ /* ======== Phase 3: SKIP GPU down projection (IQ2_S shader hangs) ========
982+ * Instead, copy the SwiGLU'd activations from gate_buf back to CPU.
983+ * The caller (tq_moe.c) will do down projection + accumulate on CPU. */
984+ memcpy (hb_output, [gate_buf contents ], inter_bytes);
985+ NSLog (@" TurboQuant MoE: Hybrid — Phase 1+2 on GPU done, returning hb for CPU down proj" );
986+ (void )output; /* unused in hybrid mode */
987+ return 1 ; /* partial success: hb_output filled, caller does down+accum */
980988 }
981989}
982990
0 commit comments