@@ -51,18 +51,25 @@ void compute_ref_swizzle(const uint8_t *h_input, uint8_t *h_output,
5151 }
5252}
5353
54- static void zero_scale_inv_padding (uint8_t *buf,
55- size_t padded_rows, size_t padded_cols,
56- size_t orig_rows, size_t orig_cols) {
54+ static void set_scale_inv_padding (uint8_t *buf,
55+ size_t padded_rows, size_t padded_cols,
56+ size_t orig_rows, size_t orig_cols,
57+ uint8_t value) {
5758 for (size_t r = 0 ; r < padded_rows; ++r) {
5859 for (size_t c = 0 ; c < padded_cols; ++c) {
5960 if (r >= orig_rows || c >= orig_cols) {
60- buf[r * padded_cols + c] = 0 ;
61+ buf[r * padded_cols + c] = value ;
6162 }
6263 }
6364 }
6465}
6566
67+ static void zero_scale_inv_padding (uint8_t *buf,
68+ size_t padded_rows, size_t padded_cols,
69+ size_t orig_rows, size_t orig_cols) {
70+ set_scale_inv_padding (buf, padded_rows, padded_cols, orig_rows, orig_cols, 0 );
71+ }
72+
6673// ===================================================================
6774// Multi-tensor swizzle test
6875// ===================================================================
@@ -141,6 +148,159 @@ void performTestMultiTensorSwizzle(const int num_tensors, const size_t M, const
141148 }
142149}
143150
151+ void performTestMultiTensorSwizzleNVFP4 (const int num_tensors, const size_t M, const size_t K,
152+ bool rowwise) {
153+ using namespace test ;
154+ constexpr size_t BLOCK_SIZE = 16 ;
155+ std::vector<std::unique_ptr<Tensor>> inputs;
156+ std::vector<std::unique_ptr<Tensor>> outputs;
157+ std::vector<NVTETensor> input_handles;
158+ std::vector<NVTETensor> output_handles;
159+
160+ for (int i = 0 ; i < num_tensors; ++i) {
161+ auto input = std::make_unique<Tensor>(" input_" + std::to_string (i),
162+ std::vector<size_t >{M, K}, DType::kFloat4E2M1 , rowwise,
163+ !rowwise, NVTE_NVFP4_1D_SCALING );
164+ auto output = std::make_unique<Tensor>(" output_" + std::to_string (i),
165+ std::vector<size_t >{M, K}, DType::kFloat4E2M1 , rowwise,
166+ !rowwise, NVTE_NVFP4_1D_SCALING );
167+ output->set_with_gemm_swizzled_scales (true );
168+ if (rowwise) {
169+ input->fill_uniform_rowwise_scale_inv ();
170+ } else {
171+ input->fill_uniform_columnwise_scale_inv ();
172+ }
173+ const NVTEShape scale_shape =
174+ rowwise ? input->rowwise_scale_inv_shape () : input->columnwise_scale_inv_shape ();
175+ uint8_t *input_scales = rowwise ? input->rowwise_cpu_scale_inv_ptr <uint8_t >()
176+ : input->columnwise_cpu_scale_inv_ptr <uint8_t >();
177+ const size_t original_rows = rowwise ? M : K;
178+ const size_t original_cols =
179+ rowwise ? (K + BLOCK_SIZE - 1 ) / BLOCK_SIZE : (M + BLOCK_SIZE - 1 ) / BLOCK_SIZE ;
180+ set_scale_inv_padding (input_scales, scale_shape.data [0 ], scale_shape.data [1 ], original_rows,
181+ original_cols, 0xFF );
182+ input->from_cpu ();
183+ input_handles.push_back (input->data ());
184+ output_handles.push_back (output->data ());
185+ inputs.emplace_back (std::move (input));
186+ outputs.emplace_back (std::move (output));
187+ }
188+
189+ nvte_multi_tensor_swizzle_scaling_factors (input_handles.data (), output_handles.data (),
190+ num_tensors, 0 );
191+ NVTE_CHECK_CUDA (cudaDeviceSynchronize ());
192+ for (int i = 0 ; i < num_tensors; ++i) {
193+ outputs[i]->to_cpu ();
194+ const NVTEShape scale_shape =
195+ rowwise ? inputs[i]->rowwise_scale_inv_shape () : inputs[i]->columnwise_scale_inv_shape ();
196+ const uint8_t *input_scales = rowwise ? inputs[i]->rowwise_cpu_scale_inv_ptr <uint8_t >()
197+ : inputs[i]->columnwise_cpu_scale_inv_ptr <uint8_t >();
198+ const uint8_t *actual = rowwise ? outputs[i]->rowwise_cpu_scale_inv_ptr <uint8_t >()
199+ : outputs[i]->columnwise_cpu_scale_inv_ptr <uint8_t >();
200+ std::vector<uint8_t > reference_input (
201+ input_scales, input_scales + scale_shape.data [0 ] * scale_shape.data [1 ]);
202+ const size_t original_rows = rowwise ? M : K;
203+ const size_t original_cols =
204+ rowwise ? (K + BLOCK_SIZE - 1 ) / BLOCK_SIZE : (M + BLOCK_SIZE - 1 ) / BLOCK_SIZE ;
205+ zero_scale_inv_padding (reference_input.data (), scale_shape.data [0 ], scale_shape.data [1 ],
206+ original_rows, original_cols);
207+ std::vector<uint8_t > reference (scale_shape.data [0 ] * scale_shape.data [1 ]);
208+ compute_ref_swizzle<128 , 4 , true >(reference_input.data (), reference.data (), scale_shape.data [0 ],
209+ scale_shape.data [1 ]);
210+ compareResults (" multi_nvfp4_swizzle" , actual, reference.data (), reference.size ());
211+ }
212+ }
213+
214+ TEST (MultiTensorSwizzleTest, MXFP8ColumnwiseBatchIgnoresUnusedRowwiseBuffer) {
215+ using namespace test ;
216+ constexpr int num_tensors = 2 ;
217+ const std::vector<size_t > shape{256 , 128 };
218+ std::vector<std::unique_ptr<Tensor>> inputs;
219+ std::vector<std::unique_ptr<Tensor>> outputs;
220+ std::vector<NVTETensor> input_handles;
221+ std::vector<NVTETensor> output_handles;
222+
223+ for (int i = 0 ; i < num_tensors; ++i) {
224+ const bool has_rowwise_scales = i == 0 ;
225+ auto input = std::make_unique<Tensor>(" input_" + std::to_string (i), shape,
226+ DType::kFloat8E4M3 , has_rowwise_scales, true ,
227+ NVTE_MXFP8_1D_SCALING );
228+ auto output = std::make_unique<Tensor>(" output_" + std::to_string (i), shape,
229+ DType::kFloat8E4M3 , has_rowwise_scales, true ,
230+ NVTE_MXFP8_1D_SCALING );
231+ if (has_rowwise_scales) input->fill_uniform_rowwise_scale_inv ();
232+ input->fill_uniform_columnwise_scale_inv ();
233+ output->set_with_gemm_swizzled_scales (true );
234+ input->from_cpu ();
235+ input_handles.push_back (input->data ());
236+ output_handles.push_back (output->data ());
237+ inputs.emplace_back (std::move (input));
238+ outputs.emplace_back (std::move (output));
239+ }
240+
241+ nvte_multi_tensor_swizzle_scaling_factors (input_handles.data (), output_handles.data (),
242+ num_tensors, 0 );
243+ NVTE_CHECK_CUDA (cudaDeviceSynchronize ());
244+ for (int i = 0 ; i < num_tensors; ++i) {
245+ outputs[i]->to_cpu ();
246+ const NVTEShape scale_shape = inputs[i]->columnwise_scale_inv_shape ();
247+ std::vector<uint8_t > reference (scale_shape.data [0 ] * scale_shape.data [1 ]);
248+ compute_ref_swizzle<128 , 4 , false >(
249+ inputs[i]->columnwise_cpu_scale_inv_ptr <uint8_t >(), reference.data (), scale_shape.data [1 ],
250+ scale_shape.data [0 ]);
251+ compareResults (" multi_mixed_buffers_columnwise_" + std::to_string (i),
252+ outputs[i]->columnwise_cpu_scale_inv_ptr <uint8_t >(), reference.data (),
253+ reference.size ());
254+ }
255+ }
256+
257+ TEST (MultiTensorSwizzleTest, NVFP4FullWidthChunkLaunchSplitting) {
258+ for (const bool rowwise : {false , true }) {
259+ performTestMultiTensorSwizzleNVFP4 (4 , 160 , 96 , rowwise);
260+ performTestMultiTensorSwizzleNVFP4 (4 , 128 , 128 , rowwise);
261+ performTestMultiTensorSwizzleNVFP4 (65 , 128 , 128 , rowwise);
262+ }
263+ }
264+
265+ TEST (MultiTensorSwizzleTest, RowwiseMixedSharedMemoryFallback) {
266+ using namespace test ;
267+ // The first tensor fits the full-width shared-memory limit, but the second does not.
268+ const std::vector<std::pair<size_t , size_t >> shapes{{128 , 7168 }, {128 , 8064 }};
269+ std::vector<std::unique_ptr<Tensor>> inputs;
270+ std::vector<std::unique_ptr<Tensor>> outputs;
271+ std::vector<NVTETensor> input_handles;
272+ std::vector<NVTETensor> output_handles;
273+ for (size_t i = 0 ; i < shapes.size (); ++i) {
274+ const auto [M, K] = shapes[i];
275+ auto input = std::make_unique<Tensor>(" input_" + std::to_string (i),
276+ std::vector<size_t >{M, K}, DType::kFloat8E4M3 , true ,
277+ false , NVTE_MXFP8_1D_SCALING );
278+ auto output = std::make_unique<Tensor>(" output_" + std::to_string (i),
279+ std::vector<size_t >{M, K}, DType::kFloat8E4M3 , true ,
280+ false , NVTE_MXFP8_1D_SCALING );
281+ output->set_with_gemm_swizzled_scales (true );
282+ input->fill_uniform_rowwise_scale_inv ();
283+ input->from_cpu ();
284+ input_handles.push_back (input->data ());
285+ output_handles.push_back (output->data ());
286+ inputs.emplace_back (std::move (input));
287+ outputs.emplace_back (std::move (output));
288+ }
289+
290+ nvte_multi_tensor_swizzle_scaling_factors (
291+ input_handles.data (), output_handles.data (), static_cast <int >(inputs.size ()), 0 );
292+ NVTE_CHECK_CUDA (cudaDeviceSynchronize ());
293+ for (size_t i = 0 ; i < inputs.size (); ++i) {
294+ outputs[i]->to_cpu ();
295+ const NVTEShape scale_shape = inputs[i]->rowwise_scale_inv_shape ();
296+ std::vector<uint8_t > reference (scale_shape.data [0 ] * scale_shape.data [1 ]);
297+ compute_ref_swizzle<128 , 4 , true >(inputs[i]->rowwise_cpu_scale_inv_ptr <uint8_t >(),
298+ reference.data (), scale_shape.data [0 ], scale_shape.data [1 ]);
299+ compareResults (" multi_rowwise_fallback" , outputs[i]->rowwise_cpu_scale_inv_ptr <uint8_t >(),
300+ reference.data (), reference.size ());
301+ }
302+ }
303+
144304// ===================================================================
145305// Multi-tensor unswizzle test (uses single-tensor swizzle to prepare)
146306// ===================================================================
@@ -300,22 +460,11 @@ void performTestMultiTensorRoundtrip(const int num_tensors, const size_t M, cons
300460// Test suites
301461// ===================================================================
302462
303- // Sets NVTE_MXFP8_PER_TILE_SWIZZLE while it lives, so that MXFP8 swizzles use the per-tile
304- // kernels (enable) or the default ones.
305- class PerTileSwizzle {
306- public:
307- explicit PerTileSwizzle (bool enable) {
308- setenv (" NVTE_MXFP8_PER_TILE_SWIZZLE" , enable ? " 1" : " 0" , 1 );
309- }
310- ~PerTileSwizzle () { unsetenv (" NVTE_MXFP8_PER_TILE_SWIZZLE" ); }
311- };
312-
313463class MultiTensorSwizzleTestSuite
314- : public ::testing::TestWithParam<std::tuple<std::tuple< int , size_t , size_t , bool > , bool >> {};
464+ : public ::testing::TestWithParam<std::tuple<int , size_t , size_t , bool >> {};
315465
316466TEST_P (MultiTensorSwizzleTestSuite, TestMultiTensorSwizzle) {
317- const auto & [num_tensors, M, K, rowwise] = std::get<0 >(GetParam ());
318- const PerTileSwizzle per_tile_swizzle (std::get<1 >(GetParam ()));
467+ const auto & [num_tensors, M, K, rowwise] = GetParam ();
319468 performTestMultiTensorSwizzle (num_tensors, M, K, rowwise);
320469}
321470
@@ -343,17 +492,7 @@ TEST_P(MultiTensorRoundtripTestSuite, TestMultiTensorRoundtrip) {
343492
344493namespace {
345494
346- // Shapes that exercise the narrow_k kernel (rowwise) / narrow_m kernel (colwise):
347- // Narrow-K fires when ALL tensors have scale num_tiles_k < TB_DIM (32),
348- // i.e. padded ceil(K/32) < 128.
349- // Narrow-M fires analogously for colwise when padded K < 4096
350- // (since colwise m = K padded to 128, num_tiles_m = m / 128 < 32).
351- //
352- // Shapes that bypass narrow and use the regular multi_tensor kernel:
353- // K >= 4096 makes num_tiles_k >= 32 (rowwise) and num_tiles_m >= 32 (colwise).
354-
355495std::vector<std::tuple<int , size_t , size_t , bool >> multi_tensor_test_cases = {
356- // --- Narrow path cases (K small → narrow_k for row, narrow_m for col) ---
357496 // M and K both aligned to 128
358497 {3 , 256 , 256 , true },
359498 {3 , 256 , 256 , false },
@@ -365,7 +504,7 @@ std::vector<std::tuple<int, size_t, size_t, bool>> multi_tensor_test_cases = {
365504 {3 , 192 , 256 , false },
366505 {2 , 64 , 256 , true },
367506 {2 , 64 , 256 , false },
368- // Larger narrow K (num_tiles_k = 8, shared mem = 128 KB)
507+ // Larger K
369508 {2 , 128 , 1024 , true },
370509 {2 , 128 , 1024 , false },
371510 // K not divisible by 128
@@ -380,13 +519,16 @@ std::vector<std::tuple<int, size_t, size_t, bool>> multi_tensor_test_cases = {
380519 {4 , 32 , 64 , true },
381520 {4 , 32 , 64 , false },
382521
383- // --- Non-narrow path cases (K >= 4096 → regular multi_tensor kernel) ---
384522 {3 , 256 , 4096 , true },
385523 {3 , 256 , 4096 , false },
386524 {2 , 128 , 8192 , true },
387525 {2 , 128 , 8192 , false },
388526 // Rowwise num_tiles_k = 34 selects vec_load_size = 2 (int2 kernel)
389527 {2 , 128 , 4352 , true },
528+ // Wide row-wise scales fall back for an underfilled grid and use the full-width kernel once
529+ // the aggregate grid is large enough.
530+ {2 , 2048 , 7168 , true }, // 32 aggregate row tiles: regular fallback.
531+ {16 , 2048 , 7168 , true }, // 256 aggregate row tiles: rowwise full-width kernel.
390532 // Colwise num_tiles_k = 512 / 32 / 4 = 4 selects vec_load_size = 4 (int4 kernel)
391533 {2 , 512 , 4096 , false },
392534 // MXFP8 swizzle: more tensors than one launch takes, several row-wise tiles per CTA,
@@ -404,14 +546,12 @@ std::vector<std::tuple<int, size_t, size_t, bool>> multi_tensor_test_cases = {
404546INSTANTIATE_TEST_SUITE_P (
405547 OperatorTest,
406548 MultiTensorSwizzleTestSuite,
407- ::testing::Combine (::testing:: ValuesIn(multi_tensor_test_cases), ::testing::Values( false , true ) ),
549+ ::testing::ValuesIn (multi_tensor_test_cases),
408550 [](const testing::TestParamInfo<MultiTensorSwizzleTestSuite::ParamType>& info) {
409- const auto & test_case = std::get<0 >(info.param );
410- return " n" + std::to_string (std::get<0 >(test_case)) +
411- " _M" + std::to_string (std::get<1 >(test_case)) +
412- " _K" + std::to_string (std::get<2 >(test_case)) +
413- (std::get<3 >(test_case) ? " _row" : " _col" ) +
414- (std::get<1 >(info.param ) ? " _per_tile" : " " );
551+ return " n" + std::to_string (std::get<0 >(info.param )) +
552+ " _M" + std::to_string (std::get<1 >(info.param )) +
553+ " _K" + std::to_string (std::get<2 >(info.param )) +
554+ (std::get<3 >(info.param ) ? " _row" : " _col" );
415555 });
416556
417557INSTANTIATE_TEST_SUITE_P (
0 commit comments