From 50077232f716d9cdfc3b53a3dd6795738fee5973 Mon Sep 17 00:00:00 2001 From: Zeref996 <825276847@qq.com> Date: Mon, 6 Jul 2026 07:39:14 +0000 Subject: [PATCH 1/3] add QA test tools --- tools/qa_test/api.yaml | 947 ++++++++ tools/qa_test/config_analyzer.py | 3708 +++++++++++++++++++++++++++++ tools/qa_test/dedup_config.py | 57 + tools/qa_test/derive_api_seq.py | 638 +++++ tools/qa_test/extract_api_set.py | 101 + tools/qa_test/merge_configs.py | 74 + tools/qa_test/run_pipeline.sh | 162 ++ tools/qa_test/to_0_size_config.py | 310 +++ tools/qa_test/verify_api_seq.py | 128 + 9 files changed, 6125 insertions(+) create mode 100644 tools/qa_test/api.yaml create mode 100644 tools/qa_test/config_analyzer.py create mode 100644 tools/qa_test/dedup_config.py create mode 100644 tools/qa_test/derive_api_seq.py create mode 100644 tools/qa_test/extract_api_set.py create mode 100644 tools/qa_test/merge_configs.py create mode 100755 tools/qa_test/run_pipeline.sh create mode 100644 tools/qa_test/to_0_size_config.py create mode 100644 tools/qa_test/verify_api_seq.py diff --git a/tools/qa_test/api.yaml b/tools/qa_test/api.yaml new file mode 100644 index 00000000..579b32a3 --- /dev/null +++ b/tools/qa_test/api.yaml @@ -0,0 +1,947 @@ +apis: + - paddle.Tensor.__abs__ + - paddle.Tensor.__add__ + - paddle.Tensor.__and__ + - paddle.Tensor.__dir__ + - paddle.Tensor.__div__ + - paddle.Tensor.__eq__ + - paddle.Tensor.__floordiv__ + - paddle.Tensor.__ge__ + - paddle.Tensor.__getitem__ + - paddle.Tensor.__gt__ + - paddle.Tensor.__le__ + - paddle.Tensor.__len__ + - paddle.Tensor.__lshift__ + - paddle.Tensor.__lt__ + - paddle.Tensor.__matmul__ + - paddle.Tensor.__mod__ + - paddle.Tensor.__mul__ + - paddle.Tensor.__ne__ + - paddle.Tensor.__neg__ + - paddle.Tensor.__nonzero__ + - paddle.Tensor.__or__ + - paddle.Tensor.__pos__ + - paddle.Tensor.__pow__ + - paddle.Tensor.__radd__ + - paddle.Tensor.__rand__ + - paddle.Tensor.__rdiv__ + - paddle.Tensor.__rfloordiv__ + - paddle.Tensor.__rlshift__ + - paddle.Tensor.__rmatmul__ + - paddle.Tensor.__rmod__ + - paddle.Tensor.__rmul__ + - paddle.Tensor.__ror__ + - paddle.Tensor.__rpow__ + - paddle.Tensor.__rrshift__ + - paddle.Tensor.__rshift__ + - paddle.Tensor.__rsub__ + - paddle.Tensor.__rtruediv__ + - paddle.Tensor.__rxor__ + - paddle.Tensor.__setitem__ + - paddle.Tensor.__sub__ + - paddle.Tensor.__truediv__ + - paddle.Tensor.__xor__ + - paddle.Tensor.abs + - paddle.Tensor.acos + - paddle.Tensor.acosh + - paddle.Tensor.add + - paddle.Tensor.add_n + - paddle.Tensor.addmm + - paddle.Tensor.all + - paddle.Tensor.allclose + - paddle.Tensor.amax + - paddle.Tensor.amin + - paddle.Tensor.angle + - paddle.Tensor.any + - paddle.Tensor.apply + - paddle.Tensor.argmax + - paddle.Tensor.argmin + - paddle.Tensor.argsort + - paddle.Tensor.as_complex + - paddle.Tensor.as_real + - paddle.Tensor.as_strided + - paddle.Tensor.asin + - paddle.Tensor.asinh + - paddle.Tensor.astype + - paddle.Tensor.atan + - paddle.Tensor.atan2 + - paddle.Tensor.atanh + - paddle.Tensor.atleast_1d + - paddle.Tensor.atleast_2d + - paddle.Tensor.atleast_3d + - paddle.Tensor.bernoulli_ + - paddle.Tensor.bincount + - paddle.Tensor.bitwise_and + - paddle.Tensor.bitwise_invert + - paddle.Tensor.bitwise_left_shift + - paddle.Tensor.bitwise_not + - paddle.Tensor.bitwise_or + - paddle.Tensor.bitwise_right_shift + - paddle.Tensor.bitwise_xor + - paddle.Tensor.block_diag + - paddle.Tensor.bmm + - paddle.Tensor.broadcast_shape + - paddle.Tensor.broadcast_tensors + - paddle.Tensor.broadcast_to + - paddle.Tensor.bucketize + - paddle.Tensor.cast + - paddle.Tensor.cauchy_ + - paddle.Tensor.cdist + - paddle.Tensor.ceil + - paddle.Tensor.cholesky + - paddle.Tensor.cholesky_inverse + - paddle.Tensor.cholesky_solve + - paddle.Tensor.chunk + - paddle.Tensor.clip + - paddle.Tensor.clone + - paddle.Tensor.coalesce + - paddle.Tensor.combinations + - paddle.Tensor.concat + - paddle.Tensor.cond + - paddle.Tensor.conj + - paddle.Tensor.copysign + - paddle.Tensor.corrcoef + - paddle.Tensor.cos + - paddle.Tensor.cosh + - paddle.Tensor.count_nonzero + - paddle.Tensor.cov + - paddle.Tensor.cross + - paddle.Tensor.cummax + - paddle.Tensor.cummin + - paddle.Tensor.cumprod + - paddle.Tensor.cumsum + - paddle.Tensor.cumulative_trapezoid + - paddle.Tensor.deg2rad + - paddle.Tensor.detach + - paddle.Tensor.diag + - paddle.Tensor.diag_embed + - paddle.Tensor.diagflat + - paddle.Tensor.diagonal + - paddle.Tensor.diagonal_scatter + - paddle.Tensor.diff + - paddle.Tensor.digamma + - paddle.Tensor.dim + - paddle.Tensor.dist + - paddle.Tensor.divide + - paddle.Tensor.dot + - paddle.Tensor.dsplit + - paddle.Tensor.eig + - paddle.Tensor.eigvals + - paddle.Tensor.eigvalsh + - paddle.Tensor.equal + - paddle.Tensor.equal_all + - paddle.Tensor.erf + - paddle.Tensor.erfinv + - paddle.Tensor.exp + - paddle.Tensor.expand + - paddle.Tensor.expand_as + - paddle.Tensor.expm1 + - paddle.Tensor.exponential_ + - paddle.Tensor.fill_ + - paddle.Tensor.fill_diagonal_ + - paddle.Tensor.fill_diagonal_tensor + - paddle.Tensor.flatten + - paddle.Tensor.flip + - paddle.Tensor.floor + - paddle.Tensor.floor_divide + - paddle.Tensor.floor_mod + - paddle.Tensor.fmax + - paddle.Tensor.fmin + - paddle.Tensor.frac + - paddle.Tensor.frexp + - paddle.Tensor.gammainc + - paddle.Tensor.gammaincc + - paddle.Tensor.gammaln + - paddle.Tensor.gather + - paddle.Tensor.gather_nd + - paddle.Tensor.gcd + - paddle.Tensor.geometric_ + - paddle.Tensor.greater_equal + - paddle.Tensor.greater_than + - paddle.Tensor.heaviside + - paddle.Tensor.histogram + - paddle.Tensor.histogram_bin_edges + - paddle.Tensor.histogramdd + - paddle.Tensor.householder_product + - paddle.Tensor.hsplit + - paddle.Tensor.hypot + - paddle.Tensor.i0 + - paddle.Tensor.i0e + - paddle.Tensor.i1 + - paddle.Tensor.i1e + - paddle.Tensor.imag + - paddle.Tensor.increment + - paddle.Tensor.index_add + - paddle.Tensor.index_fill + - paddle.Tensor.index_put + - paddle.Tensor.index_sample + - paddle.Tensor.index_select + - paddle.Tensor.inner + - paddle.Tensor.inverse + - paddle.Tensor.is_coalesced + - paddle.Tensor.is_complex + - paddle.Tensor.is_empty + - paddle.Tensor.isclose + - paddle.Tensor.isfinite + - paddle.Tensor.isin + - paddle.Tensor.isinf + - paddle.Tensor.isnan + - paddle.Tensor.isneginf + - paddle.Tensor.isposinf + - paddle.Tensor.isreal + - paddle.Tensor.istft + - paddle.Tensor.item + - paddle.Tensor.kron + - paddle.Tensor.kthvalue + - paddle.Tensor.lcm + - paddle.Tensor.ldexp + - paddle.Tensor.lerp + - paddle.Tensor.less + - paddle.Tensor.less_equal + - paddle.Tensor.less_than + - paddle.Tensor.lgamma + - paddle.Tensor.log + - paddle.Tensor.log_normal_ + - paddle.Tensor.log10 + - paddle.Tensor.log1p + - paddle.Tensor.log2 + - paddle.Tensor.logaddexp + - paddle.Tensor.logcumsumexp + - paddle.Tensor.logical_and + - paddle.Tensor.logical_not + - paddle.Tensor.logical_or + - paddle.Tensor.logical_xor + - paddle.Tensor.logit + - paddle.Tensor.logsumexp + - paddle.Tensor.lstsq + - paddle.Tensor.lu + - paddle.Tensor.lu_unpack + - paddle.Tensor.masked_fill + - paddle.Tensor.masked_scatter + - paddle.Tensor.masked_select + - paddle.Tensor.matmul + - paddle.Tensor.matrix_power + - paddle.Tensor.matrix_transpose + - paddle.Tensor.max + - paddle.Tensor.maximum + - paddle.Tensor.mean + - paddle.Tensor.median + - paddle.Tensor.min + - paddle.Tensor.minimum + - paddle.Tensor.mm + - paddle.Tensor.mod + - paddle.Tensor.mode + - paddle.Tensor.moveaxis + - paddle.Tensor.multi_dot + - paddle.Tensor.multigammaln + - paddle.Tensor.multinomial + - paddle.Tensor.multiplex + - paddle.Tensor.multiply + - paddle.Tensor.mv + - paddle.Tensor.nan_to_num + - paddle.Tensor.nanmean + - paddle.Tensor.nanmedian + - paddle.Tensor.nanquantile + - paddle.Tensor.nansum + - paddle.Tensor.neg + - paddle.Tensor.negative + - paddle.Tensor.nextafter + - paddle.Tensor.nonzero + - paddle.Tensor.norm + - paddle.Tensor.normal_ + - paddle.Tensor.not_equal + - paddle.Tensor.ormqr + - paddle.Tensor.outer + - paddle.Tensor.pca_lowrank + - paddle.Tensor.pinv + - paddle.Tensor.polar + - paddle.Tensor.polygamma + - paddle.Tensor.pow + - paddle.Tensor.prod + - paddle.Tensor.put_along_axis + - paddle.Tensor.qr + - paddle.Tensor.quantile + - paddle.Tensor.rad2deg + - paddle.Tensor.rank + - paddle.Tensor.real + - paddle.Tensor.reciprocal + - paddle.Tensor.reduce_as + - paddle.Tensor.remainder + - paddle.Tensor.renorm + - paddle.Tensor.repeat_interleave + - paddle.Tensor.reshape + - paddle.Tensor.resize_ + - paddle.Tensor.reverse + - paddle.Tensor.roll + - paddle.Tensor.rot90 + - paddle.Tensor.round + - paddle.Tensor.rsqrt + - paddle.Tensor.scale + - paddle.Tensor.scatter + - paddle.Tensor.scatter_nd + - paddle.Tensor.scatter_nd_add + - paddle.Tensor.select_scatter + - paddle.Tensor.sgn + - paddle.Tensor.shard_index + - paddle.Tensor.sigmoid + - paddle.Tensor.sign + - paddle.Tensor.signbit + - paddle.Tensor.sin + - paddle.Tensor.sinc + - paddle.Tensor.sinh + - paddle.Tensor.slice + - paddle.Tensor.slice_scatter + - paddle.Tensor.solve + - paddle.Tensor.sort + - paddle.Tensor.split + - paddle.Tensor.sqrt + - paddle.Tensor.square + - paddle.Tensor.squeeze + - paddle.Tensor.stack + - paddle.Tensor.stanh + - paddle.Tensor.std + - paddle.Tensor.stft + - paddle.Tensor.strided_slice + - paddle.Tensor.subtract + - paddle.Tensor.sum + - paddle.Tensor.svd_lowrank + - paddle.Tensor.take + - paddle.Tensor.take_along_axis + - paddle.Tensor.tan + - paddle.Tensor.tanh + - paddle.Tensor.tensor_split + - paddle.Tensor.tensordot + - paddle.Tensor.tile + - paddle.Tensor.tolist + - paddle.Tensor.top_p_sampling + - paddle.Tensor.topk + - paddle.Tensor.trace + - paddle.Tensor.transpose + - paddle.Tensor.trapezoid + - paddle.Tensor.tril + - paddle.Tensor.triu + - paddle.Tensor.trunc + - paddle.Tensor.unbind + - paddle.Tensor.unflatten + - paddle.Tensor.unfold + - paddle.Tensor.unique + - paddle.Tensor.unique_consecutive + - paddle.Tensor.unsqueeze + - paddle.Tensor.unstack + - paddle.Tensor.vander + - paddle.Tensor.var + - paddle.Tensor.view + - paddle.Tensor.view_as + - paddle.Tensor.vsplit + - paddle.Tensor.where + - paddle.Tensor.zero_ + - paddle.abs + - paddle.acos + - paddle.acosh + - paddle.add + - paddle.add_n + - paddle.addmm + - paddle.all + - paddle.allclose + - paddle.amax + - paddle.amin + - paddle.angle + - paddle.any + - paddle.arange + - paddle.argmax + - paddle.argmin + - paddle.argsort + - paddle.as_complex + - paddle.as_real + - paddle.as_strided + - paddle.asin + - paddle.asinh + - paddle.assign + - paddle.atan + - paddle.atan2 + - paddle.atanh + - paddle.atleast_1d + - paddle.atleast_2d + - paddle.atleast_3d + - paddle.audio.functional.compute_fbank_matrix + - paddle.audio.functional.create_dct + - paddle.audio.functional.fft_frequencies + - paddle.audio.functional.get_window + - paddle.audio.functional.hz_to_mel + - paddle.audio.functional.mel_frequencies + - paddle.audio.functional.mel_to_hz + - paddle.audio.functional.power_to_db + - paddle.autograd.hessian + - paddle.autograd.jacobian + - paddle.bernoulli + - paddle.bincount + - paddle.binomial + - paddle.bitwise_and + - paddle.bitwise_invert + - paddle.bitwise_left_shift + - paddle.bitwise_not + - paddle.bitwise_or + - paddle.bitwise_right_shift + - paddle.bitwise_xor + - paddle.block_diag + - paddle.bmm + - paddle.broadcast_shape + - paddle.broadcast_tensors + - paddle.broadcast_to + - paddle.bucketize + - paddle.cartesian_prod + - paddle.cast + - paddle.cauchy_ + - paddle.cdist + - paddle.ceil + - paddle.check_shape + - paddle.chunk + - paddle.clip + - paddle.clone + - paddle.column_stack + - paddle.combinations + - paddle.complex + - paddle.concat + - paddle.conj + - paddle.copysign + - paddle.cos + - paddle.cosh + - paddle.count_nonzero + - paddle.crop + - paddle.cross + - paddle.cummax + - paddle.cummin + - paddle.cumprod + - paddle.cumsum + - paddle.cumulative_trapezoid + - paddle.deg2rad + - paddle.diag + - paddle.diag_embed + - paddle.diagflat + - paddle.diagonal + - paddle.diagonal_scatter + - paddle.diff + - paddle.digamma + - paddle.dist + - paddle.divide + - paddle.dot + - paddle.dsplit + - paddle.dstack + - paddle.einsum + - paddle.empty + - paddle.empty_like + - paddle.equal + - paddle.equal_all + - paddle.erf + - paddle.erfinv + - paddle.exp + - paddle.expand + - paddle.expand_as + - paddle.expm1 + - paddle.eye + - paddle.fft.fft + - paddle.fft.fft2 + - paddle.fft.fftfreq + - paddle.fft.fftn + - paddle.fft.fftshift + - paddle.fft.hfft + - paddle.fft.hfft2 + - paddle.fft.hfftn + - paddle.fft.ifft + - paddle.fft.ifft2 + - paddle.fft.ifftn + - paddle.fft.ifftshift + - paddle.fft.ihfft + - paddle.fft.ihfft2 + - paddle.fft.ihfftn + - paddle.fft.irfft + - paddle.fft.irfft2 + - paddle.fft.irfftn + - paddle.fft.rfft + - paddle.fft.rfft2 + - paddle.fft.rfftfreq + - paddle.fft.rfftn + - paddle.flatten + - paddle.flip + - paddle.floor + - paddle.floor_divide + - paddle.floor_mod + - paddle.flops + - paddle.fmax + - paddle.fmin + - paddle.frac + - paddle.frexp + - paddle.full + - paddle.full_like + - paddle.gammainc + - paddle.gammaincc + - paddle.gammaln + - paddle.gather + - paddle.gather_nd + - paddle.gcd + - paddle.geometric.reindex_graph + - paddle.geometric.reindex_heter_graph + - paddle.geometric.sample_neighbors + - paddle.geometric.segment_max + - paddle.geometric.segment_mean + - paddle.geometric.segment_min + - paddle.geometric.segment_sum + - paddle.geometric.send_u_recv + - paddle.geometric.send_ue_recv + - paddle.geometric.send_uv + - paddle.geometric.weighted_sample_neighbors + - paddle.geometric_ + - paddle.greater_equal + - paddle.greater_than + - paddle.heaviside + - paddle.histogram + - paddle.histogram_bin_edges + - paddle.histogramdd + - paddle.hsplit + - paddle.hstack + - paddle.hypot + - paddle.i0 + - paddle.i0e + - paddle.i1 + - paddle.i1e + - paddle.imag + - paddle.increment + - paddle.incubate.nn.functional.blha_get_max_len + - paddle.incubate.nn.functional.block_multihead_attention + - paddle.incubate.nn.functional.fused_bias_act + - paddle.incubate.nn.functional.fused_bias_dropout_residual_layer_norm + - paddle.incubate.nn.functional.fused_dropout_add + - paddle.incubate.nn.functional.fused_feedforward + - paddle.incubate.nn.functional.fused_layer_norm + - paddle.incubate.nn.functional.fused_linear + - paddle.incubate.nn.functional.fused_linear_activation + - paddle.incubate.nn.functional.fused_matmul_bias + - paddle.incubate.nn.functional.fused_multi_head_attention + - paddle.incubate.nn.functional.fused_multi_transformer + - paddle.incubate.nn.functional.fused_rms_norm + - paddle.incubate.nn.functional.fused_rotary_position_embedding + - paddle.incubate.nn.functional.masked_multihead_attention + - paddle.incubate.nn.functional.swiglu + - paddle.incubate.nn.functional.variable_length_memory_efficient_attention + - paddle.incubate.segment_max + - paddle.incubate.segment_mean + - paddle.incubate.segment_min + - paddle.incubate.segment_sum + - paddle.incubate.softmax_mask_fuse + - paddle.incubate.softmax_mask_fuse_upper_triangle + - paddle.index_add + - paddle.index_fill + - paddle.index_put + - paddle.index_sample + - paddle.index_select + - paddle.inner + - paddle.is_complex + - paddle.is_empty + - paddle.isclose + - paddle.isfinite + - paddle.isin + - paddle.isinf + - paddle.isnan + - paddle.isneginf + - paddle.isposinf + - paddle.isreal + - paddle.kron + - paddle.kthvalue + - paddle.lcm + - paddle.ldexp + - paddle.lerp + - paddle.less + - paddle.less_equal + - paddle.less_than + - paddle.lgamma + - paddle.linalg.cholesky + - paddle.linalg.cholesky_inverse + - paddle.linalg.cholesky_solve + - paddle.linalg.cond + - paddle.linalg.corrcoef + - paddle.linalg.cov + - paddle.linalg.cross + - paddle.linalg.det + - paddle.linalg.diagonal + - paddle.linalg.eig + - paddle.linalg.eigh + - paddle.linalg.eigvals + - paddle.linalg.eigvalsh + - paddle.linalg.fp8_fp8_half_gemm_fused + - paddle.linalg.householder_product + - paddle.linalg.inv + - paddle.linalg.lstsq + - paddle.linalg.lu + - paddle.linalg.lu_unpack + - paddle.linalg.matrix_exp + - paddle.linalg.matrix_norm + - paddle.linalg.matrix_power + - paddle.linalg.matrix_rank + - paddle.linalg.matrix_transpose + - paddle.linalg.multi_dot + - paddle.linalg.norm + - paddle.linalg.ormqr + - paddle.linalg.pca_lowrank + - paddle.linalg.pinv + - paddle.linalg.qr + - paddle.linalg.slogdet + - paddle.linalg.solve + - paddle.linalg.svd + - paddle.linalg.svd_lowrank + - paddle.linalg.svdvals + - paddle.linalg.triangular_solve + - paddle.linalg.vecdot + - paddle.linalg.vector_norm + - paddle.linspace + - paddle.log + - paddle.log_normal + - paddle.log10 + - paddle.log1p + - paddle.log2 + - paddle.logaddexp + - paddle.logcumsumexp + - paddle.logical_and + - paddle.logical_not + - paddle.logical_or + - paddle.logical_xor + - paddle.logit + - paddle.logspace + - paddle.logsumexp + - paddle.masked_fill + - paddle.masked_scatter + - paddle.masked_select + - paddle.matmul + - paddle.matrix_transpose + - paddle.max + - paddle.maximum + - paddle.mean + - paddle.median + - paddle.meshgrid + - paddle.min + - paddle.minimum + - paddle.mm + - paddle.mod + - paddle.mode + - paddle.moveaxis + - paddle.multigammaln + - paddle.multinomial + - paddle.multiplex + - paddle.multiply + - paddle.mv + - paddle.nan_to_num + - paddle.nanmean + - paddle.nanmedian + - paddle.nanquantile + - paddle.nansum + - paddle.neg + - paddle.negative + - paddle.nextafter + - paddle.nn.dynamic_decode + - paddle.nn.functional.adaptive_avg_pool1d + - paddle.nn.functional.adaptive_avg_pool2d + - paddle.nn.functional.adaptive_avg_pool3d + - paddle.nn.functional.adaptive_log_softmax_with_loss + - paddle.nn.functional.adaptive_max_pool1d + - paddle.nn.functional.adaptive_max_pool2d + - paddle.nn.functional.adaptive_max_pool3d + - paddle.nn.functional.affine_grid + - paddle.nn.functional.alpha_dropout + - paddle.nn.functional.avg_pool1d + - paddle.nn.functional.avg_pool2d + - paddle.nn.functional.avg_pool3d + - paddle.nn.functional.batch_norm + - paddle.nn.functional.bilinear + - paddle.nn.functional.binary_cross_entropy + - paddle.nn.functional.binary_cross_entropy_with_logits + - paddle.nn.functional.celu + - paddle.nn.functional.channel_shuffle + - paddle.nn.functional.class_center_sample + - paddle.nn.functional.conv1d + - paddle.nn.functional.conv1d_transpose + - paddle.nn.functional.conv2d + - paddle.nn.functional.conv2d_transpose + - paddle.nn.functional.conv3d + - paddle.nn.functional.conv3d_transpose + - paddle.nn.functional.cosine_embedding_loss + - paddle.nn.functional.cosine_similarity + - paddle.nn.functional.cross_entropy + - paddle.nn.functional.ctc_loss + - paddle.nn.functional.dice_loss + - paddle.nn.functional.dropout + - paddle.nn.functional.dropout2d + - paddle.nn.functional.dropout3d + - paddle.nn.functional.elu + - paddle.nn.functional.embedding + - paddle.nn.functional.feature_alpha_dropout + - paddle.nn.functional.flash_attn_qkvpacked + - paddle.nn.functional.flash_attn_varlen_qkvpacked + - paddle.nn.functional.flashmask_attention + - paddle.nn.functional.fold + - paddle.nn.functional.fractional_max_pool2d + - paddle.nn.functional.fractional_max_pool3d + - paddle.nn.functional.gather_tree + - paddle.nn.functional.gaussian_nll_loss + - paddle.nn.functional.gelu + - paddle.nn.functional.glu + - paddle.nn.functional.grid_sample + - paddle.nn.functional.group_norm + - paddle.nn.functional.gumbel_softmax + - paddle.nn.functional.hardshrink + - paddle.nn.functional.hardsigmoid + - paddle.nn.functional.hardswish + - paddle.nn.functional.hardtanh + - paddle.nn.functional.hinge_embedding_loss + - paddle.nn.functional.hsigmoid_loss + - paddle.nn.functional.instance_norm + - paddle.nn.functional.interpolate + - paddle.nn.functional.kl_div + - paddle.nn.functional.l1_loss + - paddle.nn.functional.label_smooth + - paddle.nn.functional.layer_norm + - paddle.nn.functional.leaky_relu + - paddle.nn.functional.linear + - paddle.nn.functional.local_response_norm + - paddle.nn.functional.log_loss + - paddle.nn.functional.log_sigmoid + - paddle.nn.functional.log_softmax + - paddle.nn.functional.lp_pool1d + - paddle.nn.functional.lp_pool2d + - paddle.nn.functional.margin_cross_entropy + - paddle.nn.functional.margin_ranking_loss + - paddle.nn.functional.max_pool1d + - paddle.nn.functional.max_pool2d + - paddle.nn.functional.max_pool3d + - paddle.nn.functional.max_unpool1d + - paddle.nn.functional.max_unpool2d + - paddle.nn.functional.max_unpool3d + - paddle.nn.functional.maxout + - paddle.nn.functional.mish + - paddle.nn.functional.mse_loss + - paddle.nn.functional.multi_label_soft_margin_loss + - paddle.nn.functional.multi_margin_loss + - paddle.nn.functional.nll_loss + - paddle.nn.functional.normalize + - paddle.nn.functional.npair_loss + - paddle.nn.functional.one_hot + - paddle.nn.functional.pad + - paddle.nn.functional.pairwise_distance + - paddle.nn.functional.pixel_shuffle + - paddle.nn.functional.pixel_unshuffle + - paddle.nn.functional.poisson_nll_loss + - paddle.nn.functional.prelu + - paddle.nn.functional.relu + - paddle.nn.functional.relu6 + - paddle.nn.functional.rnnt_loss + - paddle.nn.functional.rrelu + - paddle.nn.functional.scaled_dot_product_attention + - paddle.nn.functional.selu + - paddle.nn.functional.sequence_mask + - paddle.nn.functional.sigmoid + - paddle.nn.functional.sigmoid_focal_loss + - paddle.nn.functional.silu + - paddle.nn.functional.smooth_l1_loss + - paddle.nn.functional.soft_margin_loss + - paddle.nn.functional.softmax + - paddle.nn.functional.softmax_with_cross_entropy + - paddle.nn.functional.softplus + - paddle.nn.functional.softshrink + - paddle.nn.functional.softsign + - paddle.nn.functional.sparse_attention + - paddle.nn.functional.square_error_cost + - paddle.nn.functional.swish + - paddle.nn.functional.tanh + - paddle.nn.functional.tanhshrink + - paddle.nn.functional.temporal_shift + - paddle.nn.functional.thresholded_relu + - paddle.nn.functional.triplet_margin_loss + - paddle.nn.functional.triplet_margin_with_distance_loss + - paddle.nn.functional.unfold + - paddle.nn.functional.upsample + - paddle.nn.functional.zeropad2d + - paddle.nn.quant.llm_int8_linear + - paddle.nn.quant.weight_dequantize + - paddle.nn.quant.weight_only_linear + - paddle.nn.quant.weight_quantize + - paddle.nn.utils.clip_grad_norm_ + - paddle.nn.utils.clip_grad_value_ + - paddle.nn.utils.parameters_to_vector + - paddle.nn.utils.remove_weight_norm + - paddle.nn.utils.spectral_norm + - paddle.nn.utils.vector_to_parameters + - paddle.nn.utils.weight_norm + - paddle.nonzero + - paddle.normal + - paddle.not_equal + - paddle.numel + - paddle.ones + - paddle.ones_like + - paddle.onnx.export + - paddle.outer + - paddle.pdist + - paddle.poisson + - paddle.polar + - paddle.polygamma + - paddle.positive + - paddle.pow + - paddle.prod + - paddle.put_along_axis + - paddle.quantile + - paddle.rad2deg + - paddle.rank + - paddle.real + - paddle.reciprocal + - paddle.reduce_as + - paddle.remainder + - paddle.renorm + - paddle.repeat_interleave + - paddle.reshape + - paddle.reverse + - paddle.roll + - paddle.rot90 + - paddle.round + - paddle.row_stack + - paddle.rsqrt + - paddle.scale + - paddle.scatter + - paddle.scatter_nd + - paddle.scatter_nd_add + - paddle.searchsorted + - paddle.select_scatter + - paddle.sgn + - paddle.shape + - paddle.shard_index + - paddle.sign + - paddle.signal.istft + - paddle.signal.stft + - paddle.signbit + - paddle.sin + - paddle.sinc + - paddle.sinh + - paddle.slice + - paddle.slice_scatter + - paddle.sort + - paddle.sparse.abs + - paddle.sparse.add + - paddle.sparse.addmm + - paddle.sparse.asin + - paddle.sparse.asinh + - paddle.sparse.atan + - paddle.sparse.atanh + - paddle.sparse.cast + - paddle.sparse.coalesce + - paddle.sparse.deg2rad + - paddle.sparse.divide + - paddle.sparse.expm1 + - paddle.sparse.isnan + - paddle.sparse.log1p + - paddle.sparse.mask_as + - paddle.sparse.masked_matmul + - paddle.sparse.matmul + - paddle.sparse.multiply + - paddle.sparse.mv + - paddle.sparse.neg + - paddle.sparse.nn.functional.attention + - paddle.sparse.nn.functional.conv2d + - paddle.sparse.nn.functional.conv3d + - paddle.sparse.nn.functional.leaky_relu + - paddle.sparse.nn.functional.max_pool3d + - paddle.sparse.nn.functional.relu + - paddle.sparse.nn.functional.relu6 + - paddle.sparse.nn.functional.softmax + - paddle.sparse.nn.functional.subm_conv2d + - paddle.sparse.nn.functional.subm_conv2d_igemm + - paddle.sparse.nn.functional.subm_conv3d + - paddle.sparse.nn.functional.subm_conv3d_igemm + - paddle.sparse.pca_lowrank + - paddle.sparse.pow + - paddle.sparse.rad2deg + - paddle.sparse.reshape + - paddle.sparse.sin + - paddle.sparse.sinh + - paddle.sparse.slice + - paddle.sparse.sqrt + - paddle.sparse.square + - paddle.sparse.subtract + - paddle.sparse.sum + - paddle.sparse.tan + - paddle.sparse.tanh + - paddle.sparse.transpose + - paddle.split + - paddle.sqrt + - paddle.square + - paddle.squeeze + - paddle.stack + - paddle.standard_gamma + - paddle.standard_normal + - paddle.stanh + - paddle.std + - paddle.strided_slice + - paddle.subtract + - paddle.sum + - paddle.summary + - paddle.t + - paddle.take + - paddle.take_along_axis + - paddle.tan + - paddle.tanh + - paddle.tensor_split + - paddle.tensordot + - paddle.tile + - paddle.tolist + - paddle.topk + - paddle.trace + - paddle.transpose + - paddle.trapezoid + - paddle.tril + - paddle.tril_indices + - paddle.triu + - paddle.triu_indices + - paddle.trunc + - paddle.unbind + - paddle.unflatten + - paddle.unfold + - paddle.unique + - paddle.unique_consecutive + - paddle.unsqueeze + - paddle.unstack + - paddle.vander + - paddle.var + - paddle.vecdot + - paddle.view + - paddle.view_as + - paddle.vision.ops.box_coder + - paddle.vision.ops.decode_jpeg + - paddle.vision.ops.deform_conv2d + - paddle.vision.ops.distribute_fpn_proposals + - paddle.vision.ops.generate_proposals + - paddle.vision.ops.matrix_nms + - paddle.vision.ops.nms + - paddle.vision.ops.prior_box + - paddle.vision.ops.psroi_pool + - paddle.vision.ops.roi_align + - paddle.vision.ops.roi_pool + - paddle.vision.ops.yolo_box + - paddle.vision.ops.yolo_loss + - paddle.vision.transforms.adjust_brightness + - paddle.vision.transforms.adjust_contrast + - paddle.vision.transforms.adjust_hue + - paddle.vision.transforms.affine + - paddle.vision.transforms.center_crop + - paddle.vision.transforms.crop + - paddle.vision.transforms.erase + - paddle.vision.transforms.hflip + - paddle.vision.transforms.normalize + - paddle.vision.transforms.perspective + - paddle.vision.transforms.rotate + - paddle.vision.transforms.to_grayscale + - paddle.vision.transforms.to_tensor + - paddle.vision.transforms.vflip + - paddle.vsplit + - paddle.vstack + - paddle.where + - paddle.zeros + - paddle.zeros_like + - paddle._C_ops._run_custom_op + - paddle.nn.functional.* + - paddle.incubate.nn.functional.* +# - paddle._C_ops.* # 默认关闭(上层接口已覆盖大多数场景);若担心遗漏底层算子可取消注释 diff --git a/tools/qa_test/config_analyzer.py b/tools/qa_test/config_analyzer.py new file mode 100644 index 00000000..045705f3 --- /dev/null +++ b/tools/qa_test/config_analyzer.py @@ -0,0 +1,3708 @@ +from __future__ import annotations + +import collections +import copy +import math +import os +import random +import re + +import numpy +import paddle +import torch + +USE_CACHED_NUMPY = os.getenv("USE_CACHED_NUMPY", "False").lower() == "true" +TEST_NON_CONTIGUOUS = os.getenv("TEST_NON_CONTIGUOUS", "0").lower() in ("true", "1") +USE_GPU_CACHE_MODE = os.getenv("USE_GPU_CACHE_MODE", "False").lower() == "true" +SKIP_GPU_CLEANUP = os.getenv("SKIP_GPU_CLEANUP", "False").lower() == "true" +cached_numpy = {} +cached_gpu_inputs = {} + + +def _env_bool(name, default=False): + return os.getenv(name, str(default)).lower() in ("true", "1", "yes", "y") + + +def set_gpu_cache_mode(enabled): + os.environ["USE_GPU_CACHE_MODE"] = str(bool(enabled)) + os.environ["SKIP_GPU_CLEANUP"] = str(bool(enabled)) + + +def is_gpu_cache_mode(): + return _env_bool("USE_GPU_CACHE_MODE", USE_GPU_CACHE_MODE) + + +def should_skip_gpu_cleanup(): + return _env_bool("SKIP_GPU_CLEANUP", SKIP_GPU_CLEANUP) + + +def clear_gpu_cache(): + cached_gpu_inputs.clear() + + +def _shape_tuple(shape): + return tuple(int(dim) for dim in shape) + + +def _numel(shape): + numel = 1 + for dim in shape: + numel *= int(dim) + return numel + + +def _normalize_cache_dtype(dtype): + if dtype in ["float8_e5m2", "float8_e4m3fn"]: + return "float16" + if dtype == "bfloat16": + return "float32" + return str(dtype) + + +def get_cached_numpy_array( + dtype, + shape, + generation_kind="input", + scale=1.2, + int_low=-65535, + int_high=65535, +): + dtype = _normalize_cache_dtype(dtype) + shape = _shape_tuple(shape) + key = (dtype, shape, generation_kind, float(scale), int(int_low), int(int_high)) + if key in cached_numpy: + return cached_numpy[key] + + if "int" in dtype: + tensor = numpy.random.randint(int_low, int_high, size=shape, dtype="int64").astype(dtype) + elif dtype.startswith("complex"): + real_dtype = "float32" if dtype == "complex64" else "float64" + real_part = ((numpy.random.random(shape) - 0.5) * scale).astype(real_dtype) + imag_part = ((numpy.random.random(shape) - 0.5) * scale).astype(real_dtype) + tensor = (real_part + 1j * imag_part).astype(dtype) + else: + tensor = ((numpy.random.random(shape) - 0.5) * scale).astype(dtype) + cached_numpy[key] = tensor + return tensor + + +# Optimizer APIs that need special tensor initialization to avoid NaN +# Format: {api_name: {arg_index: init_method}} +# init_method: "zeros" = fill with 0, "small_positive" = fill with small positive value +optimizer_apis = { + # moment1, moment2, moment2_max (must be non-negative for amsgrad) + "paddle._C_ops.adamw_": {3: "zeros", 4: "zeros", 5: "zeros"}, + "paddle._C_ops.adam_": {3: "zeros", 4: "zeros", 5: "zeros"}, + "paddle._C_ops.merged_adam_": {3: "zeros", 4: "zeros", 5: "zeros"}, +} + +not_zero_apis = frozenset( + [ + "paddle.Tensor.__div__", + "paddle.Tensor.__floordiv__", + "paddle.Tensor.__mod__", + "paddle.Tensor.__rdiv__", + "paddle.Tensor.__rfloordiv__", + "paddle.Tensor.__rmod__", + "paddle.Tensor.__rtruediv__", + "paddle.Tensor.__truediv__", + "paddle.Tensor.divide", + "paddle.Tensor.floor_divide", + "paddle.Tensor.floor_mod", + "paddle.Tensor.mod", + "paddle.divide", + "paddle.floor_divide", + "paddle.floor_mod", + "paddle.mod", + "paddle.nn.functional.kl_div", + "paddle.sparse.divide", + ] +) + + +def generate_unique_array(num_items, float_dtype): + def get_integer_dtype(float_dtype): + float_dtype = numpy.dtype(float_dtype) + if float_dtype == numpy.float16: + return numpy.uint16, 16 + elif float_dtype == numpy.float32: + return numpy.uint32, 32 + elif float_dtype == numpy.float64: + return numpy.uint64, 64 + else: + raise ValueError(f"Unsupported float dtype: {float_dtype}") + + integer_dtype, bits = get_integer_dtype(float_dtype) + max_int = (1 << bits) - 1 + current_start_value = 1 + return_list = [] + attempt_count = 0 + while len(return_list) < num_items and attempt_count < 3: + nums_to_generate = int(num_items * 1.5) + if current_start_value >= max_int: + raise ValueError( + f"Cannot generate {num_items} unique items of type {float_dtype} within the range." + ) + end_value = min(current_start_value + nums_to_generate, max_int) + random_arr = numpy.arange(current_start_value, end_value, dtype=integer_dtype) + float_arr = random_arr.view(float_dtype) + if return_list is None: + return_list = float_arr[numpy.isfinite(float_arr)] + else: + return_list = numpy.unique( + numpy.concatenate([return_list, float_arr[numpy.isfinite(float_arr)]]) + ) + current_start_value = end_value + attempt_count += 1 + if len(return_list) < num_items: + raise ValueError(f"Could not generate {num_items} unique items of type {float_dtype}") + return return_list[:num_items] + + +class TensorConfig: + def __init__(self, shape, dtype, place=None, is_contiguous=True, strides=None): + self.shape = shape + self.dtype = dtype + self.place = place + self.is_contiguous = is_contiguous + self.strides = strides + self.numpy_tensor = None + self.paddle_tensor = None + self.torch_tensor = None + self.shuffle_dims = None + + def __deepcopy__(self, memo): + cls = self.__class__ + result = cls.__new__(cls) + memo[id(self)] = result + result.shape = copy.deepcopy(self.shape) + result.dtype = copy.deepcopy(self.dtype) + result.place = copy.deepcopy(self.place) + result.is_contiguous = self.is_contiguous + result.strides = copy.deepcopy(self.strides) + return result + + def __str__(self): + return f'Tensor({self.shape},"{self.dtype}")' + + def __repr__(self): + return f'Tensor({self.shape},"{self.dtype}")' + + def convert_dtype_to_torch_type(self, dtype): + if dtype in ["float32", numpy.float32]: + return torch.float32 + elif dtype in ["float16", numpy.float16]: + return torch.float16 + elif dtype in ["float64", numpy.float64]: + return torch.float64 + elif dtype in ["int16", numpy.int16]: + return torch.int16 + elif dtype in ["int8", numpy.int8]: + return torch.int8 + elif dtype in ["bool", numpy.bool_]: + return torch.bool + elif dtype in ["bfloat16", numpy.uint16]: + return torch.bfloat16 + elif dtype in ["uint8", numpy.uint8]: + return torch.uint8 + elif dtype in ["int32", numpy.int32]: + return torch.int32 + elif dtype in ["int64", numpy.int64]: + return torch.int64 + elif dtype in ["complex64", numpy.complex64]: + return torch.complex64 + elif dtype in ["complex128", numpy.complex128]: + return torch.complex128 + elif dtype == "float8_e4m3fn": + return torch.float8_e4m3fn + elif dtype == "float8_e5m2": + return torch.float8_e5m2 + else: + raise ValueError(f"Unsupported dtype: {dtype}") + + def numel(self): + return _numel(self.shape) + + def get_cached_numpy(self, dtype, shape, generation_kind="input", scale=1.2): + return get_cached_numpy_array(dtype, shape, generation_kind=generation_kind, scale=scale) + + def _use_gpu_cache(self, dtype=None): + dtype = dtype or self.dtype + if not is_gpu_cache_mode(): + return False + if not torch.cuda.is_available(): + return False + if self.place is not None and "cpu" in str(self.place).lower(): + return False + if not self.is_contiguous or self.strides is not None: + return False + if dtype in ["float8_e5m2", "float8_e4m3fn"]: + return False + return True + + def _gpu_cache_key(self, api_config, dtype=None): + dtype = dtype or self.dtype + location = ( + getattr(self, "index", None), + getattr(self, "key", None), + tuple(getattr(self, "list_index", [])), + ) + return (api_config.config, location, dtype, _shape_tuple(self.shape)) + + def _make_gpu_cache_tensors(self, dtype=None): + dtype = dtype or self.dtype + torch_dtype = self.convert_dtype_to_torch_type(dtype) + shape = tuple(self.shape) + device = torch.device("cuda", torch.cuda.current_device()) + if dtype == "bool": + torch_tensor = torch.randint(0, 2, shape, device=device, dtype=torch.int8).to( + torch.bool + ) + elif "int" in dtype or dtype == "uint8": + torch_tensor = torch.randint(-65535, 65535, shape, device=device, dtype=torch.int64).to( + dtype=torch_dtype + ) + elif dtype.startswith("complex"): + real_dtype = torch.float32 if dtype == "complex64" else torch.float64 + real_part = (torch.rand(shape, device=device, dtype=real_dtype) - 0.5) * 1.2 + imag_part = (torch.rand(shape, device=device, dtype=real_dtype) - 0.5) * 1.2 + torch_tensor = (real_part + 1j * imag_part).to(dtype=torch_dtype) + else: + base = (torch.rand(shape, device=device, dtype=torch.float32) - 0.5) * 1.2 + torch_tensor = base.to(dtype=torch_dtype) + + torch_source = torch_tensor.detach() + paddle_tensor = paddle.utils.dlpack.from_dlpack( + torch.utils.dlpack.to_dlpack(torch_source.clone()) + ) + paddle_tensor.stop_gradient = False + torch_tensor = torch_source.clone() + if dtype in ["float32", "float64", "float16", "complex64", "complex128", "bfloat16"]: + torch_tensor = torch_tensor.requires_grad_(True) + return paddle_tensor, torch_tensor + + def _get_gpu_cache_entry(self, api_config, dtype=None): + dtype = dtype or self.dtype + key = self._gpu_cache_key(api_config, dtype) + if key not in cached_gpu_inputs: + paddle_tensor, torch_tensor = self._make_gpu_cache_tensors(dtype) + cached_gpu_inputs[key] = { + "paddle": paddle_tensor, + "torch": torch_tensor, + } + return cached_gpu_inputs[key] + + def get_gpu_paddle_tensor(self, api_config, dtype=None): + entry = self._get_gpu_cache_entry(api_config, dtype) + self.paddle_tensor = entry["paddle"] + return self.paddle_tensor + + def get_gpu_torch_tensor(self, api_config, dtype=None): + entry = self._get_gpu_cache_entry(api_config, dtype) + self.torch_tensor = entry["torch"] + return self.torch_tensor + + def generate_random_axes(self, api_config): + x_shape = self.get_arg(api_config, 0, "x").shape + max_dim = max(len(x_shape), 1) # scalar + + if len(self.shape) == 0: + dim = numpy.random.randint(0, max_dim) + if numpy.random.rand() > 0.5: + dim -= max_dim + return numpy.array(dim, dtype=self.dtype) + + if len(self.shape) == 1: + dims = numpy.random.choice(max_dim, size=self.shape[0], replace=False) + mask = numpy.random.rand(self.shape[0]) > 0.5 + dims = numpy.where(mask, dims - max_dim, dims) + return numpy.array(dims, dtype=self.dtype) + + raise ValueError( + f"Invalid shape for 'axis' Tensor in {api_config.api_name}. " + f"Expected a 0-D or 1-D Tensor, but got shape {self.shape}." + ) + + def generate_random_index(self, api_config, allow_none=False): + axis = self.get_arg(api_config, 2, "axis") + if axis is None and not allow_none: + raise ValueError("Axis is None") + + x_shape = self.get_arg(api_config, 0, "x").shape + axis = axis if axis >= 0 else axis + len(x_shape) + if not (0 <= axis < len(x_shape)): + raise ValueError(f"Invalid axis {axis} for shape {x_shape}") + if len(self.shape) >= 1: + return numpy.random.randint(0, x_shape[axis], size=self.shape, dtype=self.dtype) + + raise ValueError( + f"Invalid shape for 'index' Tensor in {api_config.api_name}. " + f"Expected a 0-D or 1-D Tensor, but got shape {self.shape}." + ) + + def get_random_axis_on_tensor(self, api_config, arg_pos, kwargs_name): + cfg = self.get_arg(api_config, arg_pos, kwargs_name) + if isinstance(cfg, TensorConfig): + max_idx = len(cfg.shape) + return self.get_random_numpy_tensor([], data_type=self.dtype, min=0, max=max_idx) + else: + raise ValueError(f"Invalid axis config={cfg} in {api_config.api_name}") + + def get_numpy_tensor(self, api_config, index=None, key=None, **kwargs): + if index is not None: + self.index = index + if key is not None: + self.key = key + if "list_index" in kwargs: + self.list_index = kwargs["list_index"] + + original_dtype = self.dtype + if self.dtype in ["float8_e5m2", "float8_e4m3fn"]: + # numpy doesn't support float8, use float16 as intermediate + self.dtype = "float16" + elif self.dtype == "bfloat16": + self.dtype = "float32" + + if self.numpy_tensor is None: + if api_config.api_name in {"paddle.Tensor.view", "paddle.view"}: + if ( + self.check_arg(api_config, 0, "x") + and original_dtype == "uint8" + and str(self.get_arg(api_config, 1, "shape_or_dtype", "")) == "paddle.bfloat16" + ): + bf16_numel = math.prod(self.shape) // 2 + finite_float32 = ((numpy.random.random(bf16_numel) - 0.5) * 1.2).astype( + "float32" + ) + self.numpy_tensor = ( + (finite_float32.view("uint32") >> 16).astype("uint16").view("uint8") + ) + elif ( + api_config.api_name in optimizer_apis + and self.index in optimizer_apis[api_config.api_name] + ): + self.numpy_tensor = numpy.zeros(self.shape).astype(self.dtype) + elif api_config.api_name in not_zero_apis: + if "int" in self.dtype: + if self.dtype == "int8": + arr = numpy.random.randint(1, 256, size=self.shape, dtype=numpy.int32) + # 128-255 -> -128~-1 + arr[arr > 127] -= 256 + self.numpy_tensor = arr.astype(self.dtype) + elif self.dtype == "uint8": + self.numpy_tensor = numpy.random.randint(1, 256, size=self.shape).astype( + self.dtype + ) + else: + self.numpy_tensor = ( + numpy.random.randint(1, 65535, size=self.shape) + ).astype(self.dtype) + else: + if self.dtype.startswith("complex"): + real_dtype = "float32" if self.dtype == "complex64" else "float64" + real_part = (numpy.random.random(self.shape) + 0.5).astype(real_dtype) + imag_part = (numpy.random.random(self.shape) + 0.5).astype(real_dtype) + self.numpy_tensor = (real_part + 1j * imag_part).astype(self.dtype) + else: + self.numpy_tensor = (numpy.random.random(self.shape) + 0.5).astype( + self.dtype + ) + elif api_config.api_name == "paddle._C_ops.adamw_": + if self.check_arg(api_config, 6, "beta1_pow") or self.check_arg( + api_config, 7, "beta2_pow" + ): + if not hasattr(api_config, "adamw_step"): + api_config.adamw_step = numpy.random.randint(1, 101) + beta = self.get_arg(api_config, 10, "beta1") + if self.check_arg(api_config, 7, "beta2_pow"): + beta = self.get_arg(api_config, 11, "beta2") + self.numpy_tensor = numpy.full( + self.shape, + beta**api_config.adamw_step, + dtype=self.dtype, + ) + + elif api_config.api_name == "paddle.arange": + start_val = self.get_arg(api_config, 0, "start", 0) + end_val = self.get_arg(api_config, 1, "end", None) + step_val = self.get_arg(api_config, 2, "step", 1) + + def generate_step_tensor(step_config, is_positive): + if "int" in step_config.dtype: + if is_positive: + return numpy.random.randint(1, 10, step_config.shape).astype( + step_config.dtype + ) + else: + return numpy.random.randint(-10, -1, step_config.shape).astype( + step_config.dtype + ) + else: + if is_positive: + return numpy.random.uniform(0.1, 5.0, step_config.shape).astype( + step_config.dtype + ) + else: + return numpy.random.uniform(-5.0, -0.1, step_config.shape).astype( + step_config.dtype + ) + + def safe_range(low, high): + max_range = 100 + if high - low > max_range: + if low < 0: + high = low + max_range + else: + low = high - max_range + if low >= high: + low = high - 10 + return max(low, -1000), min(high, 1000) + + if isinstance(start_val, TensorConfig): + if isinstance(end_val, TensorConfig): + if isinstance(step_val, TensorConfig): + flag = numpy.random.choice([True, False]) + step_val.numpy_tensor = generate_step_tensor(step_val, flag) + else: + flag = step_val > 0 + if "int" in start_val.dtype: + start_val.numpy_tensor = numpy.random.randint( + -50, 50, start_val.shape + ).astype(start_val.dtype) + else: + start_val.numpy_tensor = numpy.random.uniform( + -50.0, 50.0, start_val.shape + ).astype(start_val.dtype) + start = start_val.numpy_tensor.item() + if flag: + low, high = safe_range(start + 1, start + 50) + if "int" in end_val.dtype: + end_val.numpy_tensor = numpy.random.randint( + low, high, end_val.shape + ).astype(end_val.dtype) + else: + end_val.numpy_tensor = numpy.random.uniform( + low, high, end_val.shape + ).astype(end_val.dtype) + else: + low, high = safe_range(start - 50, start - 1) + if "int" in end_val.dtype: + end_val.numpy_tensor = numpy.random.randint( + low, high, end_val.shape + ).astype(end_val.dtype) + else: + end_val.numpy_tensor = numpy.random.uniform( + low, high, end_val.shape + ).astype(end_val.dtype) + elif end_val is None: + if isinstance(step_val, TensorConfig): + flag = numpy.random.choice([True, False]) + step_val.numpy_tensor = generate_step_tensor(step_val, flag) + else: + flag = step_val > 0 + if flag: + if "int" in start_val.dtype: + start_val.numpy_tensor = numpy.random.randint( + 1, 50, start_val.shape + ).astype(start_val.dtype) + else: + start_val.numpy_tensor = numpy.random.uniform( + 0.1, 50.0, start_val.shape + ).astype(start_val.dtype) + else: + if "int" in start_val.dtype: + start_val.numpy_tensor = numpy.random.randint( + -50, -1, start_val.shape + ).astype(start_val.dtype) + else: + start_val.numpy_tensor = numpy.random.uniform( + -50.0, -0.1, start_val.shape + ).astype(start_val.dtype) + else: + if isinstance(step_val, TensorConfig): + flag = numpy.random.choice([True, False]) + step_val.numpy_tensor = generate_step_tensor(step_val, flag) + else: + flag = step_val > 0 + if flag: + low, high = safe_range(end_val - 50, end_val - 1) + if "int" in start_val.dtype: + start_val.numpy_tensor = numpy.random.randint( + low, high, start_val.shape + ).astype(start_val.dtype) + else: + start_val.numpy_tensor = numpy.random.uniform( + low, high, start_val.shape + ).astype(start_val.dtype) + else: + low, high = safe_range(end_val + 1, end_val + 50) + if "int" in start_val.dtype: + start_val.numpy_tensor = numpy.random.randint( + low, high, start_val.shape + ).astype(start_val.dtype) + else: + start_val.numpy_tensor = numpy.random.uniform( + low, high, start_val.shape + ).astype(start_val.dtype) + else: + if isinstance(end_val, TensorConfig): + if isinstance(step_val, TensorConfig): + flag = numpy.random.choice([True, False]) + step_val.numpy_tensor = generate_step_tensor(step_val, flag) + else: + flag = step_val > 0 + if flag: + low, high = safe_range(start_val + 1, start_val + 50) + if "int" in end_val.dtype: + end_val.numpy_tensor = numpy.random.randint( + low, high, end_val.shape + ).astype(end_val.dtype) + else: + end_val.numpy_tensor = numpy.random.uniform( + low, high, end_val.shape + ).astype(end_val.dtype) + else: + low, high = safe_range(start_val - 50, start_val - 1) + if "int" in end_val.dtype: + end_val.numpy_tensor = numpy.random.randint( + low, high, end_val.shape + ).astype(end_val.dtype) + else: + end_val.numpy_tensor = numpy.random.uniform( + low, high, end_val.shape + ).astype(end_val.dtype) + elif end_val is None: + if isinstance(step_val, TensorConfig): + flag = start_val > 0 + step_val.numpy_tensor = generate_step_tensor(step_val, flag) + else: + pass + else: + if isinstance(step_val, TensorConfig): + flag = start_val < end_val + step_val.numpy_tensor = generate_step_tensor(step_val, flag) + else: + pass + + dtype_val = self.get_arg(api_config, 3, "dtype") + if ( + dtype_val + and "int" in str(dtype_val) + and isinstance(step_val, TensorConfig) + and "int" not in step_val.dtype + ): + if step_val.numpy_tensor.item() > 0: + step_val.numpy_tensor = numpy.random.uniform( + 1.0, 5.0, step_config.shape + ).astype(step_config.dtype) + else: + step_val.numpy_tensor = numpy.random.uniform( + -5.0, -1.0, step_config.shape + ).astype(step_config.dtype) + + elif api_config.api_name in { + "paddle.argmax", + "paddle.argmin", + "paddle.Tensor.argmax", + "paddle.Tensor.argmin", + }: + if self.check_arg(api_config, 1, "axis"): + arr = self.get_arg(api_config, 0, "x") + min_dim = len(arr.shape) + self.numpy_tensor = numpy.random.randint( + -min_dim, min_dim - 1, size=self.shape + ).astype("int64") + self.dtype = "int64" + + elif api_config.api_name == "paddle.atan2": + if self.check_arg(api_config, 0, "x"): + s1 = self.get_arg(api_config, 0, "x") + s1 = s1.shape + self.numpy_tensor = (numpy.random.random(s1) + 1).astype(self.dtype) + elif self.check_arg(api_config, 1, "y"): + s2 = self.get_arg(api_config, 1, "y") + s2 = s2.shape + self.numpy_tensor = (numpy.random.random(s2) + 1).astype(self.dtype) + + elif api_config.api_name == "paddle.bernoulli": + if self.check_arg(api_config, 0, "x"): + self.numpy_tensor = numpy.random.random(self.shape).astype(self.dtype) + elif api_config.api_name == "paddle.bincount": + if self.check_arg(api_config, 0, "x"): + if "int" in self.dtype: + self.numpy_tensor = numpy.random.randint(0, 65535, size=self.shape).astype( + self.dtype + ) + else: + raise ValueError( + f"The input of paddle.bincount must be of integer type, but the current type is {self.dtype}" + ) + elif self.check_arg(api_config, 2, "minlength") or self.check_arg( + api_config, None, "minlength" + ): + if "int" in self.dtype: + self.numpy_tensor = numpy.random.randint(0, 65535, size=self.shape).astype( + self.dtype + ) + else: + dtype = "int64" + self.numpy_tensor = numpy.random.randint(0, 65535, size=self.shape).astype( + dtype + ) + self.dtype = dtype + elif api_config.api_name == "paddle.incubate.nn.functional.block_multihead_attention": + qkv_shape = self.get_arg( + api_config, 0, "qkv" + ).shape # [token_num, 3 * num_head * head_size]. + bs = self.get_arg(api_config, 3, "seq_lens_encoder").shape[0] + seq_len = qkv_shape[0] // bs + + if ( + self.check_arg(api_config, 1, "key_cache") + or self.check_arg(api_config, 2, "value_cache") + or self.check_arg(api_config, 4, "seq_lens_decoder") + or self.check_arg(api_config, 10, "block_tables") + ): + self.numpy_tensor = numpy.zeros(self.shape).astype(self.dtype) + elif self.check_arg(api_config, 3, "seq_lens_encoder"): + self.numpy_tensor = numpy.array([seq_len] * bs, dtype=self.dtype) + elif self.check_arg(api_config, 5, "seq_lens_this_time"): + self.numpy_tensor = self.get_initialized_value( + api_config, 3, "seq_lens_encoder" + ) + elif self.check_arg(api_config, 6, "padding_offsets"): + padding_offsets_dtype = self.get_arg(api_config, 6, "padding_offsets").dtype + cum_offsets_dtype = self.get_arg(api_config, 7, "cum_offsets").dtype + cu_seqlens_q_dtype = self.get_arg(api_config, 8, "cu_seqlens_q").dtype + cu_seqlens_k_dtype = self.get_arg(api_config, 9, "cu_seqlens_k").dtype + seq_lens_this_time = self.get_initialized_value( + api_config, 5, "seq_lens_this_time" + ) + + def get_padding_offset(bsz, max_seq_len, seq_lens_this_time): + cum_offsets_now = numpy.cumsum(max_seq_len - seq_lens_this_time) + cum_offsets = numpy.zeros(shape=(bsz + 1), dtype=cum_offsets_dtype) + cum_offsets[1:] = cum_offsets_now + token_num = numpy.sum(seq_lens_this_time) + padding_offsets = numpy.zeros( + shape=(token_num), dtype=padding_offsets_dtype + ) + cu_seqlens_q = numpy.zeros(shape=(bsz + 1), dtype=cu_seqlens_q_dtype) + cu_seqlens_k = numpy.zeros(shape=(bsz + 1), dtype=cu_seqlens_k_dtype) + for i in range(bsz): + seq_len_now = seq_lens_this_time[i] + cum_offset = cum_offsets[i] + for j in range(seq_len_now): + padding_offsets[i * max_seq_len - cum_offset + j] = cum_offset + cum_seq_len = (i + 1) * max_seq_len - cum_offsets[i + 1] + cu_seqlens_q[i + 1] = cum_seq_len + cu_seqlens_k[i + 1] = cum_seq_len + return ( + padding_offsets, + cum_offsets[:-1], + cu_seqlens_q, + cu_seqlens_k, + ) + + padding_offset, cum_offset, cu_seqlens_q, cu_seqlens_k = get_padding_offset( + bs, seq_len, seq_lens_this_time + ) + self.numpy_tensor = padding_offset + self.set_tensor_arg_value(api_config, 7, "cum_offsets", cum_offset) + self.set_tensor_arg_value(api_config, 8, "cu_seqlens_q", cu_seqlens_q) + self.set_tensor_arg_value(api_config, 9, "cu_seqlens_k", cu_seqlens_k) + elif ( + self.check_arg(api_config, 13, "cache_k_quant_scales") + or self.check_arg(api_config, 14, "cache_v_quant_scales") + or self.check_arg(api_config, 15, "cache_k_dequant_scales") + or self.check_arg(api_config, 16, "cache_v_dequant_scales") + or self.check_arg(api_config, 17, "qkv_out_scale") + or self.check_arg(api_config, 20, "out_smooth") + ): + self.numpy_tensor = self.get_random_numpy_tensor(self.shape, self.dtype, min=0) + elif self.check_arg(api_config, 22, "max_dec_len_this_time") or self.check_arg( + api_config, 21, "max_enc_len_this_time" + ): + self.place = "cpu" + if self.check_arg(api_config, 22, "max_dec_len_this_time"): + self.numpy_tensor = numpy.zeros(self.shape).astype(self.dtype) + else: # 21, "max_enc_len_this_time" + self.numpy_tensor = numpy.array([seq_len] * bs, dtype=self.dtype) + elif self.check_arg(api_config, 23, "rope_emb") and self.place == "cpu": + self.place = "gpu" + elif self.check_arg(api_config, 24, "mask") or self.check_arg( + api_config, 25, "tgt_mask" + ): + eps = numpy.finfo(self.dtype).eps + self.numpy_tensor = self.get_random_numpy_tensor( + self.shape, self.dtype, max=0 + eps + ) + + # c + elif api_config.api_name == "paddle.chunk": + import random + + if self.check_arg(api_config, 2, "axis"): + x_tensor = self.get_arg(api_config, 0, "x") + chunks = self.get_arg(api_config, 1, "chunks") + valid_axes = [] + for i, dim_size in enumerate(x_tensor.shape): + if dim_size % chunks == 0: + valid_axes.append(i) + if not valid_axes: + raise ValueError( + f"No valid axis found in x.shape = {x_tensor.shape} for chunks = {chunks}. " + f"Each dim must be divisible by chunks." + ) + chosen_axis = random.choice(valid_axes) + if len(self.shape) == 0: + self.numpy_tensor = numpy.array(chosen_axis, dtype=self.dtype) + elif len(self.shape) == 1: + if self.shape[0] == 1: + self.numpy_tensor = numpy.array([chosen_axis], dtype=self.dtype) + else: + raise ValueError( + f"Invalid shape for 'axis' Tensor in paddle.chunk. " + f"Expected a 0-D or 1-D Tensor with 1 element, but got shape {self.shape}." + ) + else: + raise ValueError( + f"Invalid shape for 'axis' Tensor in paddle.chunk. " + f"Expected a 0-D or 1-D Tensor, but got shape {self.shape}." + ) + + elif api_config.api_name == "paddle.nn.functional.conv2d_transpose": + if (index is not None and index == 0) or key == "x": + if not hasattr(api_config, "x"): + if "int" in self.dtype: + self.numpy_tensor = ( + numpy.random.randint(-65535, 65535, size=self.shape) + ).astype(self.dtype) + else: + self.numpy_tensor = (numpy.random.random(self.shape) - 0.5).astype( + self.dtype + ) + api_config.x = self.numpy_tensor + elif (index is not None and index == 1) or key == "weight": + if not hasattr(api_config, "weight"): + if "int" in self.dtype: + self.numpy_tensor = ( + numpy.random.randint(-65535, 65535, size=self.shape) + ).astype(self.dtype) + else: + self.numpy_tensor = (numpy.random.random(self.shape) - 0.5).astype( + self.dtype + ) + api_config.weight = self.numpy_tensor + elif (index is not None and index == 2) or key == "bias": + if not hasattr(api_config, "bias"): + if "int" in self.dtype: + self.numpy_tensor = ( + numpy.random.randint(-65535, 65535, size=self.shape) + ).astype(self.dtype) + else: + self.numpy_tensor = (numpy.random.random(self.shape) - 0.5).astype( + self.dtype + ) + api_config.bias = self.numpy_tensor + elif key == "output_size": + if not hasattr(api_config, "bias"): + bias = None + else: + bias = paddle.to_tensor(api_config.bias) + stride = api_config.kwargs.get("stride", 1) + padding = api_config.kwargs.get("padding", 0) + if "dilation" in api_config.kwargs: + dilation = api_config.kwargs["dilation"] + else: + dilation = 1 + groups = api_config.kwargs.get("groups", 1) + if "output_padding" in api_config.kwargs: + output_padding = api_config.kwargs["output_padding"] + else: + output_padding = 0 + if "data_format" in api_config.kwargs: + data_format = api_config.kwargs["data_format"] + else: + data_format = "NCHW" + + output_size = paddle.nn.functional.conv2d_transpose( + paddle.to_tensor(api_config.x), + paddle.to_tensor(api_config.weight), + bias=bias, + stride=stride, + padding=padding, + output_padding=output_padding, + groups=groups, + dilation=dilation, + data_format=data_format, + ) + + last = [0, 0] + last[0] = output_size.shape[data_format.find("H")] + last[1] = output_size.shape[data_format.find("W")] + s = [1, 1] + if isinstance(stride, int): + s[0] = stride + s[1] = stride + else: + s = stride + self.numpy_tensor = numpy.zeros(self.shape).astype(self.dtype) + self.numpy_tensor[0] = numpy.random.randint(last[0], last[0] + s[0]) + self.numpy_tensor[1] = numpy.random.randint(last[1], last[1] + s[1]) + + elif api_config.api_name in {"paddle.cumsum", "paddle.Tensor.cumsum"}: + if self.check_arg(api_config, 1, "axis"): + # special args[1] tensor init, for the rest reuse default initialization logic + x_tensor_config = self.get_arg(api_config, 0, "x") + len_shape = len(x_tensor_config.shape) + self.numpy_tensor = numpy.random.randint(-len_shape, len_shape, size=self.shape) + + elif api_config.api_name in { + "paddle.clip", + "paddle.Tensor.clip", + } and self.check_arg(api_config, 0, "x"): + # if both min and max need a Tensor instead of None, init min and max at the same TensorConfig numpy tensor init process + min_config = self.get_arg(api_config, 1, "min") + max_config = self.get_arg(api_config, 2, "max") + if isinstance(min_config, TensorConfig) and isinstance(max_config, TensorConfig): + min_shape = min_config.shape + min_dtype = min_config.dtype + min_numpy_tensor = self.get_random_numpy_tensor( + shape=min_shape, data_type=min_dtype + ) + + max_shape = max_config.shape + max_dtype = max_config.dtype + max_numpy_tensor = self.get_random_numpy_tensor( + shape=max_shape, data_type=max_dtype, min=min_numpy_tensor + ) + + self.set_tensor_arg_value(api_config, 1, "min", min_numpy_tensor) + self.set_tensor_arg_value(api_config, 2, "max", max_numpy_tensor) + elif min_config is not None and max_config is not None: + # min and max args are specified but at least one of them is scalar (not a TensorConfig) + # according to API DOC, min and max is float|int|Tensor + if isinstance(min_config, TensorConfig) and ( + isinstance(max_config, (int, float)) + ): + min_shape = min_config.shape + min_dtype = min_config.dtype + min_numpy_tensor = self.get_random_numpy_tensor( + shape=min_shape, data_type=min_dtype, max=max_config + ) + self.set_tensor_arg_value(api_config, 1, "min", min_numpy_tensor) + elif isinstance(max_config, TensorConfig) and ( + isinstance(min_config, (int, float)) + ): + max_shape = max_config.shape + max_dtype = max_config.dtype + max_numpy_tensor = self.get_random_numpy_tensor( + shape=max_shape, data_type=max_dtype, min=min_config + ) + self.set_tensor_arg_value(api_config, 2, "max", max_numpy_tensor) + # for both min and max are scalar, there is no need to init numpy tensor + # init input tensor x randomly + self.numpy_tensor = self.get_random_numpy_tensor( + shape=self.shape, data_type=self.dtype + ) + elif api_config.api_name == "paddle.vision.ops.distribute_fpn_proposals": + if (index is not None and index == 0) or (key is not None and key == "fpn_rois"): + num = self.shape[0] + self.numpy_tensor = numpy.random.randint(1, 1024, [num, 4]) + self.numpy_tensor[:, 0] = self.numpy_tensor[:, 0] + numpy.random.random([num]) + self.numpy_tensor[:, 1] = self.numpy_tensor[:, 1] + numpy.random.random([num]) + self.numpy_tensor[:, 2] = ( + self.numpy_tensor[:, 0] + + numpy.random.randint(1, 1024, [num]) + + numpy.random.random([num]) + ) + self.numpy_tensor[:, 3] = ( + self.numpy_tensor[:, 1] + + numpy.random.randint(1, 1024, [num]) + + numpy.random.random([num]) + ) + if not hasattr(api_config, "num"): + api_config.num = num + elif (index is not None and index == 6) or (key is not None and key == "rois_num"): + num = api_config.num + re = self.shape[0] + self.numpy_tensor = numpy.zeros(self.shape) + if num > 4096 or re > 4096: + if num < re: + self.numpy_tensor[:num] = 1 + else: + self.numpy_tensor += num // re + self.numpy_tensor[: num % re] += 1 + else: + if num < re: + indices = numpy.random.choice(re, num, replace=False) + self.numpy_tensor[indices] = 1 + else: + for i in range(self.shape[0] - 1): + self.numpy_tensor[i] = numpy.random.randint(1, num - re + 2) + num = num - self.numpy_tensor[i] + re -= 1 + self.numpy_tensor[self.shape[0] - 1] = num + + elif api_config.api_name == "paddle.dot": + if "int" in self.dtype: + self.numpy_tensor = (numpy.random.randint(-127, 127, size=self.shape)).astype( + self.dtype + ) + + elif api_config.api_name in [ + "paddle.nn.functional.dropout", + "paddle.nn.functional.dropout2d", + "paddle.nn.functional.dropout3d", + ]: + if self.check_arg(api_config, 1, "p"): + eps = 0.1 + self.numpy_tensor = self.get_random_numpy_tensor( + shape=self.shape, data_type=self.dtype, min=0, max=1 + eps + ) + # include 1 in numpy tensor + self.numpy_tensor = numpy.where(self.numpy_tensor > 1, 1, self.numpy_tensor) + elif api_config.api_name == "paddle.nn.functional.dropout" and self.check_arg( + api_config, 2, "axis" + ): + self.numpy_tensor = self.get_random_axis_on_tensor(api_config, 0, "x") + elif api_config.api_name == "paddle.empty": + is_shape_param = False + if len(api_config.args) > 0: + if self.check_arg(api_config, 0, "shape"): + is_shape_param = True + elif isinstance(api_config.args[0], list): + for item in api_config.args[0]: + if str(item) == str(self): + is_shape_param = True + break + if "shape" in api_config.kwargs: + if str(api_config.kwargs["shape"]) == str(self): + is_shape_param = True + elif isinstance(api_config.kwargs["shape"], list): + for item in api_config.kwargs["shape"]: + if str(item) == str(self): + is_shape_param = True + break + if is_shape_param: + if "int" in self.dtype: + self.numpy_tensor = numpy.random.randint(1, 10, size=self.shape).astype( + self.dtype + ) + else: + dtype = "int32" + self.numpy_tensor = numpy.random.randint(1, 10, size=self.shape).astype( + dtype + ) + self.dtype = dtype + elif api_config.api_name == "paddle.eye": + self.numpy_tensor = numpy.random.randint(0, 2048, size=self.shape) + + elif api_config.api_name in {"paddle.expand", "paddle.Tensor.expand"}: + if key == "shape" or index == 1: + d = self.get_arg(api_config, 0, "x") + s = d.shape + ind = kwargs["list_index"][0] if "list_index" in kwargs else 0 + if len(s) == 0 or ind > len(s) - 1 or s[ind] == 1: + self.numpy_tensor = (numpy.random.randint(1, 127, size=self.shape)).astype( + self.dtype + ) + else: + if len(self.shape) == 0 or self.shape[0] == 1: + self.numpy_tensor = numpy.array(s[ind]) + else: + self.numpy_tensor = ( + numpy.random.randint(1, 127, size=self.shape) + ).astype(self.dtype) + dis = self.shape[0] - len(s) + for i in range(self.shape[0]): + if i >= dis and s[i - dis] != 1: + self.numpy_tensor[i] = s[i - dis] + + elif api_config.api_name == "paddle.full": + if self.check_arg(api_config, 1, "fill_value"): + if "int" in self.dtype: + self.numpy_tensor = ( + numpy.random.randint(1, 65535, size=self.shape) + ).astype(self.dtype) + else: + self.numpy_tensor = (numpy.random.random(self.shape) + 0.5).astype( + self.dtype + ) + else: + self.numpy_tensor = (numpy.random.randint(0, 64, size=self.shape)).astype( + self.dtype + ) + elif api_config.api_name in {"paddle.gammainc", "paddle.gammaincc"}: + if "int" in self.dtype: + self.numpy_tensor = numpy.random.randint(0, 65535, size=self.shape).astype( + self.dtype + ) + else: + self.numpy_tensor = numpy.abs(numpy.random.random(self.shape)).astype( + self.dtype + ) + elif api_config.api_name == "paddle.vision.ops.generate_proposals": + if ( + (index is not None and index == 0) + or key == "scores" + or (index is not None and index == 1) + or key == "bbox_deltas" + ): + self.numpy_tensor = numpy.random.random(self.shape).astype(self.dtype) + elif (index is not None and index == 2) or key == "img_size": + self.numpy_tensor = numpy.random.randint(0, 1024, size=self.shape).astype( + self.dtype + ) + elif (index is not None and index == 3) or key == "anchors": + self.numpy_tensor = numpy.zeros(self.shape).astype(self.dtype) + w = self.shape[0] + h = self.shape[1] + for i in range(self.shape[0]): + self.numpy_tensor[i][0] = numpy.random.random() * w + self.numpy_tensor[i][1] = numpy.random.random() * h + self.numpy_tensor[i][2] = ( + numpy.random.random() * (w - self.numpy_tensor[i][0] + 1) + + self.numpy_tensor[i][0] + + 1 + ) + self.numpy_tensor[i][3] = ( + numpy.random.random() * (h - self.numpy_tensor[i][1] + 1) + + self.numpy_tensor[i][1] + + 1 + ) + + elif api_config.api_name.startswith("paddle.geometric.segment_"): + if self.check_arg(api_config, 1, "segment_ids"): + batch_size = self.get_arg(api_config, 0, "x").shape[0] + max_segments = numpy.random.randint(1, batch_size + 1) + self.numpy_tensor = numpy.sort( + numpy.random.randint(0, max_segments, size=self.shape).astype(self.dtype) + ) + elif api_config.api_name == "paddle.geometric.sample_neighbors": + if self.check_arg(api_config, 0, "row"): + colptr_shape = self.get_arg(api_config, 1, "colptr").shape + num_nodes = colptr_shape[0] - 1 + self.numpy_tensor = numpy.random.randint( + 0, num_nodes, size=self.shape, dtype=self.dtype + ) + elif self.check_arg(api_config, 1, "colptr"): + num_edges = self.get_arg(api_config, 0, "row").shape[0] + num_nodes = self.shape[0] - 1 + colptr = numpy.zeros(self.shape, dtype=self.dtype) + if num_nodes > 0 and num_edges > 0: + splits = numpy.random.choice( + numpy.arange(num_edges + 1), num_nodes - 1, replace=True + ) + splits.sort() + colptr[1:num_nodes] = splits + colptr[num_nodes] = num_edges + self.numpy_tensor = colptr + elif self.check_arg(api_config, 2, "input_nodes"): + num_nodes = self.shape[0] - 1 + self.numpy_tensor = numpy.random.randint( + 0, num_nodes, size=self.shape, dtype=self.dtype + ) + elif self.check_arg(api_config, 4, "eids") or self.check_arg( + api_config, 6, "perm_buffer" + ): + num_edges = self.get_arg(api_config, 0, "row").shape[0] + self.numpy_tensor = numpy.arange(num_edges, dtype=self.dtype).reshape( + self.shape + ) + + elif api_config.api_name.startswith("paddle.geometric.send_"): + if api_config.api_name.endswith("u_recv"): + if self.check_arg(api_config, 1, "src_index") or self.check_arg( + api_config, 2, "dst_index" + ): + num_nodes = self.get_arg(api_config, 0, "x").shape[0] + self.numpy_tensor = numpy.random.randint( + 0, num_nodes, size=self.shape + ).astype(self.dtype) + elif self.check_arg(api_config, 2, "src_index") or self.check_arg( + api_config, 3, "dst_index" + ): + num_nodes = self.get_arg(api_config, 0, "x").shape[0] + self.numpy_tensor = numpy.random.randint(0, num_nodes, size=self.shape).astype( + self.dtype + ) + elif api_config.api_name in {"paddle.index_add", "paddle.index_fill"}: + if self.check_arg(api_config, 1, "index"): + self.numpy_tensor = self.generate_random_index(api_config) + elif api_config.api_name == "paddle.index_sample": + if self.check_arg(api_config, 1, "index"): + x_dim = self.get_arg(api_config, 0, "x").shape[1] + self.numpy_tensor = numpy.random.randint(0, x_dim, size=self.shape) + elif api_config.api_name.startswith("paddle.incubate.segment_"): + if self.check_arg(api_config, 1, "segment_ids"): + batch_size = self.get_arg(api_config, 0, "x").shape[0] + max_segments = numpy.random.randint(1, batch_size + 1) + self.numpy_tensor = numpy.sort( + numpy.random.randint(0, max_segments, size=self.shape).astype(self.dtype) + ) + elif api_config.api_name == "paddle.logspace": + if self.check_arg(api_config, 2, "num"): + self.numpy_tensor = numpy.random.randint(1, 65535, size=self.shape) + elif api_config.api_name.startswith("paddle.linalg."): + if api_config.api_name.endswith("cholesky"): + if self.check_arg(api_config, 0, "x"): + if len(self.shape) < 2 or self.shape[-1] != self.shape[-2]: + raise ValueError( + "Shape must have at least 2 dimensions and last two dimensions must be equal" + ) + batch_dims = self.shape[:-2] + matrix_dim = self.shape[-1] + A = numpy.random.random([*batch_dims, matrix_dim, matrix_dim]).astype( + self.dtype + ) + if len(batch_dims) > 0: + tensor = numpy.einsum("...ij,...kj->...ik", A, A) + else: + tensor = numpy.dot(A, A.T) + tensor += numpy.eye(matrix_dim, dtype=self.dtype) * 10000 + print("cholesky tensor", tensor) + self.numpy_tensor = tensor + elif api_config.api_name.endswith("cov"): + if self.check_arg(api_config, 0, "x"): + if len(self.shape) < 1 or len(self.shape) > 2: + raise ValueError( + "Shape must have 1 or 2 dimensions for covariance input" + ) + tensor = numpy.random.random(self.shape).astype(self.dtype) + tensor += numpy.random.random(self.shape).astype(self.dtype) * 1e-6 + self.numpy_tensor = tensor + elif self.check_arg(api_config, 3, "fweights"): + x_shape = self.get_arg(api_config, 0, "x").shape + rowvar = self.get_arg(api_config, 1, "rowvar") + if rowvar is None: + rowvar = True + n_observations = ( + (x_shape[1] if rowvar else x_shape[0]) + if len(x_shape) > 1 + else x_shape[0] + ) + self.numpy_tensor = numpy.random.randint( + 1, 11, size=(n_observations,) + ).astype(self.dtype) + elif self.check_arg(api_config, 4, "aweights"): + x_shape = self.get_arg(api_config, 0, "x").shape + rowvar = self.get_arg(api_config, 1, "rowvar") + if rowvar is None: + rowvar = True + n_observations = ( + (x_shape[1] if rowvar else x_shape[0]) + if len(x_shape) > 1 + else x_shape[0] + ) + if self.dtype in ["float32", "float64"]: + self.numpy_tensor = numpy.random.uniform( + 0.1, 1.0, size=(n_observations,) + ).astype(self.dtype) + else: + self.numpy_tensor = numpy.random.randint( + 1, 11, size=(n_observations,) + ).astype(self.dtype) + elif api_config.api_name.endswith("eigh") or api_config.api_name.endswith( + "eigvalsh" + ): + if self.check_arg(api_config, 0, "x"): + if len(self.shape) < 2 or self.shape[-1] != self.shape[-2]: + raise ValueError( + "Shape must have at least 2 dimensions and last two dimensions must be equal" + ) + batch_dims = self.shape[:-2] + matrix_dim = self.shape[-1] + A = numpy.random.random([*batch_dims, matrix_dim, matrix_dim]).astype( + self.dtype + ) + if self.dtype in ["complex64", "complex128"]: + A = A + 1j * numpy.random.random( + [*batch_dims, matrix_dim, matrix_dim] + ).astype(self.dtype) + tensor = A + A.swapaxes(-1, -2).conj() # A + A^H + else: + if len(batch_dims) > 0: + tensor = numpy.einsum("...ij,...kj->...ik", A, A) + else: + tensor = numpy.dot(A, A.T) + tensor += numpy.eye(matrix_dim, dtype=self.dtype) * 1e-6 + self.numpy_tensor = tensor + elif api_config.api_name.endswith("lstsq"): + if self.check_arg(api_config, 0, "x") or self.check_arg(api_config, 1, "y"): + if len(self.shape) < 2: + raise ValueError("Shape must have at least 2 dimensions for lstsq x") + batch_dims = self.shape[:-2] + M, N = self.shape[-2], self.shape[-1] + self.numpy_tensor = numpy.random.random([*batch_dims, M, N]).astype( + self.dtype + ) + elif api_config.api_name.endswith("lu_unpack"): + if self.check_arg(api_config, 0, "x"): + if len(self.shape) < 2: + raise ValueError("Shape must have at least 2 dimensions for LU matrix") + batch_dims = self.shape[:-2] + LU_tensor = numpy.random.random(self.shape).astype(self.dtype) + K = min(self.shape[-2], self.shape[-1]) + LU_tensor[..., range(K), range(K)] += 1e-6 + self.numpy_tensor = LU_tensor + if self.check_arg(api_config, 1, "pivot"): + M = self.get_arg(api_config, 0, "x").shape[-2] + self.numpy_tensor = numpy.random.randint(1, M + 1, size=self.shape).astype( + self.dtype + ) + elif api_config.api_name.endswith("pca_lowrank"): + self.numpy_tensor = numpy.random.randn(*self.shape).astype(self.dtype) + elif api_config.api_name.endswith("cond"): + # produce non-singular matrix + n = self.shape[-1] + # Generate random matrix, value in [0, 1) + self.numpy_tensor = numpy.random.random(self.shape).astype(self.dtype) + # Create scaled identity matrix, value is n + eye_matrix = n * numpy.eye(n, dtype=self.dtype) + # Construct a non-singular matrix: A = random_matrix + n*I + # strict diagonal dominant matrix is non-singular. https://en.wikipedia.org/wiki/Diagonally_dominant_matrix + self.numpy_tensor += eye_matrix + elif api_config.api_name.endswith("det"): + if self.check_arg(api_config, 0, "x"): + assert len(self.shape) >= 2, "Input must be at least 2D." + assert self.shape[-1] == self.shape[-2], "Input must be square matrices." + n = self.shape[-1] + is_complex = self.dtype.startswith("complex") + if is_complex: + real_dtype = ( + numpy.float32 if self.dtype == "complex64" else numpy.float64 + ) + A_real = numpy.random.uniform(0.5, 1.0, size=self.shape).astype( + real_dtype + ) + A_imag = numpy.random.uniform(0.5, 1.0, size=self.shape).astype( + real_dtype + ) + A = (A_real + 1j * A_imag).astype(self.dtype) + else: + A = numpy.random.uniform(low=0.5, high=1.0, size=self.shape).astype( + self.dtype + ) + A_H = ( + numpy.conj(A).swapaxes(-1, -2) + if is_complex + else numpy.swapaxes(A, -1, -2) + ) + self.numpy_tensor = numpy.matmul(A, A_H) + numpy.eye(n, dtype=self.dtype) + elif api_config.api_name.endswith("pinv"): + if self.check_arg(api_config, 0, "x") and self.get_arg( + api_config, 2, " hermitian" + ): + is_complex = self.dtype.startswith("complex") + if len(self.shape) not in [2, 3]: + raise ValueError("pinv only supports 2D or 3D tensors") + if is_complex: + if self.dtype == "complex64": + real_dtype = numpy.float32 + elif self.dtype == "complex128": + real_dtype = numpy.float64 + A_real = numpy.random.randn(*self.shape).astype(real_dtype) + A_imag = numpy.random.randn(*self.shape).astype(real_dtype) + A = A_real + 1j * A_imag + A = A.astype(self.dtype) + else: + A = numpy.random.randn(*self.shape).astype(self.dtype) + if len(self.shape) == 2: + A_T = A.conj().T if is_complex else A.T + else: + A_T = ( + numpy.conj(A).swapaxes(-2, -1) if is_complex else A.swapaxes(-2, -1) + ) + self.numpy_tensor = (A + A_T) / 2 + elif api_config.api_name.endswith("corrcoef"): + if self.dtype == "float16": + # 1e-3 to avoid inf + self.numpy_tensor = ( + numpy.random.randn(*self.shape).astype(self.dtype) * 1e-3 + ) + elif api_config.api_name == "paddle.linspace": + if "int" in self.dtype: + self.numpy_tensor = (numpy.random.randint(0, 65535, size=self.shape)).astype( + self.dtype + ) + else: + self.numpy_tensor = (numpy.random.random(self.shape)).astype(self.dtype) + # m + elif api_config.api_name == "paddle.incubate.nn.functional.masked_multihead_attention": + if self.check_arg(api_config, 4, "sequence_lengths"): + self.numpy_tensor = self.get_random_numpy_tensor(self.shape, self.dtype, min=1) + elif self.check_arg(api_config, 5, "rotary_tensor"): + self.numpy_tensor = self.get_random_numpy_tensor( + self.shape, self.dtype, min=0, max=1000 + ) + + elif api_config.api_name == "paddle.matrix_transpose": + if self.check_arg(api_config, 0, "x"): + if len(self.shape) < 2: + matrix_shape = [2, 2] + if "int" in self.dtype: + self.numpy_tensor = numpy.random.randint( + -65535, 65535, size=matrix_shape + ).astype(self.dtype) + else: + self.numpy_tensor = (numpy.random.random(matrix_shape) - 0.5).astype( + self.dtype + ) + else: + if "int" in self.dtype: + self.numpy_tensor = ( + numpy.random.randint(-65535, 65535, size=self.shape) + ).astype(self.dtype) + else: + self.numpy_tensor = (numpy.random.random(self.shape) - 0.5).astype( + self.dtype + ) + elif api_config.api_name in {"paddle.mean", "paddle.max", "paddle.min"}: + if self.check_arg(api_config, 1, "axis"): + self.numpy_tensor = self.generate_random_axes(api_config) + + elif api_config.api_name == "paddle.multinomial": + if self.check_arg(api_config, 0, "x"): + self.numpy_tensor = numpy.abs(numpy.random.random(self.shape)).astype( + self.dtype + ) + + if key == "num_samples" or index == 1: + if ( + "replacement" in api_config.kwargs + and self.get_arg(api_config, 2, "replacement") == True + ): + max_allow = 1024 + else: + inputs = self.get_arg(api_config, 0, "x") + inputs = inputs.numpy_tensor + max_allow = (inputs > 0).sum().item() + self.numpy_tensor = numpy.random.randint( + 1, max_allow + 1, size=self.shape + ).astype(self.dtype) + + elif api_config.api_name == "paddle.multiplex": + s = self.get_arg(api_config, 0, "inputs") + if key == "index" or index == 1: + self.numpy_tensor = (numpy.random.randint(0, len(s), size=self.shape)).astype( + self.dtype + ) + + elif api_config.api_name == "paddle.multiply": + if self.dtype == "bfloat16": + self.dtype = "float32" + + if self.dtype in ["complex64", "complex128"]: + real_dtype = "float32" if self.dtype == "complex64" else "float64" + real_part = numpy.random.random(self.shape).astype(real_dtype) + imag_part = numpy.random.random(self.shape).astype(real_dtype) + self.numpy_tensor = (real_part + 1j * imag_part).astype(self.dtype) + + else: + self.numpy_tensor = numpy.random.random(self.shape).astype(self.dtype) + + elif api_config.api_name in { + "paddle.nn.functional.max_unpool1d", + "paddle.nn.functional.max_unpool2d", + "paddle.nn.functional.max_unpool3d", + } and self.check_arg(api_config, 0, "x"): + # use max_pool to generate legal max_unpool input + kernel_size = self.get_initialized_value(api_config, 2, "kernel_size") + stride = self.get_initialized_value(api_config, 3, "stride") + padding = self.get_initialized_value(api_config, 4, "padding") + padding = 0 if padding is None else padding + stride = kernel_size if stride is None else stride + unpool_output_size = self.get_initialized_value(api_config, 5, "output_size") + pool_input_size = unpool_output_size + + ndim = 1 + if "max_unpool2d" in api_config.api_name: + ndim = 2 + elif "max_unpool3d" in api_config.api_name: + ndim = 3 + if isinstance(kernel_size, int): + kernel_size = [kernel_size] * ndim + if isinstance(stride, int): + stride = [stride] * ndim + if isinstance(padding, int): + padding = [padding] * ndim + + # if max_unpool output_size (max_pool input_size) is not set, calculate manually + unpool_input_size = self.get_arg(api_config, 0, "x").shape + pool_output_size = unpool_input_size + if pool_input_size is None: + if ndim == 1: + w_in = pool_output_size[-1] + w_out = (w_in - 1) * stride[0] - 2 * padding[0] + kernel_size[0] + pool_input_size = [*pool_output_size[:-1], w_out] + elif ndim == 2: + h_in, w_in = pool_output_size[-2], pool_output_size[-1] + h_out = (h_in - 1) * stride[0] - 2 * padding[0] + kernel_size[0] + w_out = (w_in - 1) * stride[1] - 2 * padding[1] + kernel_size[1] + pool_input_size = [*pool_output_size[:-2], h_out, w_out] + else: + d_in, h_in, w_in = ( + pool_output_size[-3], + pool_output_size[-2], + pool_output_size[-1], + ) + d_out = (d_in - 1) * stride[0] - 2 * padding[0] + kernel_size[0] + h_out = (h_in - 1) * stride[1] - 2 * padding[1] + kernel_size[1] + w_out = (w_in - 1) * stride[2] - 2 * padding[2] + kernel_size[2] + pool_input_size = [*pool_output_size[:-3], d_out, h_out, w_out] + elif len(pool_input_size) == ndim: + # fill the lost dimensions since unpool_output_size has only last ndim dims + pool_input_size = [ + *pool_output_size[:-ndim], + *pool_input_size[-ndim:], + ] + elif len(pool_input_size) != len(pool_output_size): + raise ValueError( + f"invalid argument output_size {pool_input_size} for {api_config.api_name}, len(output_size) should be {ndim} or {len(pool_output_size)} or output_size == None, got len(output_size)={len(pool_input_size)} and output_size={unpool_output_size}" + ) + + # int64 handle + data_type = "float64" if self.dtype == "int64" else self.dtype + x = paddle.to_tensor( + self.get_random_numpy_tensor( + shape=pool_input_size, data_type=data_type, min=-5, max=5 + ) + ) + max_poolxd_func = eval(api_config.api_name.replace("max_unpool", "max_pool")) + x, indices = max_poolxd_func(x, kernel_size, stride, padding, return_mask=True) + self.numpy_tensor = x.numpy() + self.set_tensor_arg_value(api_config, 1, "indices", indices) + + elif api_config.api_name == "paddle.vision.ops.nms": + if index == 0 or key == "boxes": + self.numpy_tensor = numpy.zeros(self.shape).astype(self.dtype) + for i in range(self.shape[0]): + self.numpy_tensor[i][0] = numpy.random.random() * 1023 + self.numpy_tensor[i][1] = numpy.random.random() * 1023 + self.numpy_tensor[i][2] = ( + numpy.random.random() * (1024 - self.numpy_tensor[i][0] + 1) + + self.numpy_tensor[i][0] + + 1 + ) + self.numpy_tensor[i][3] = ( + numpy.random.random() * (1024 - self.numpy_tensor[i][1] + 1) + + self.numpy_tensor[i][1] + + 1 + ) + elif index == 3 or key == "scores": + self.numpy_tensor = numpy.random.random(self.shape).astype(self.dtype) + else: + self.numpy_tensor = numpy.random.randint(0, 1024, self.shape).astype(self.dtype) + + elif api_config.api_name in { + "paddle.nn.functional.adaptive_avg_pool2d", + "paddle.nn.functional.adaptive_avg_pool3d", + }: + if key == "output_size" or index == 1: + s = self.get_arg(api_config, 0, "x") + s = s.shape + self.numpy_tensor = numpy.random.randint( + 1, 2 * numpy.max(s), size=self.shape + ).astype(self.dtype) + elif api_config.api_name == "paddle.nn.functional.adaptive_log_softmax_with_loss": + if self.check_arg(api_config, 1, "label"): + cutoffs = self.get_arg(api_config, 4, "cutoffs") + n_classes = cutoffs[-1] + generation_size = self.shape + if isinstance(self.shape, (list, tuple)) and len(self.shape) == 0: + generation_size = 1 + if n_classes == 1: + self.numpy_tensor = numpy.zeros(generation_size, dtype=self.dtype) + else: + self.numpy_tensor = numpy.random.randint( + low=0, + high=n_classes, + size=generation_size, + dtype=self.dtype, + ) + elif api_config.api_name == "paddle.nn.functional.affine_grid": + if key == "out_shape" or index == 1: + s = self.get_arg(api_config, 0, "theta") + s = s.shape + self.numpy_tensor = numpy.random.randint(1, 128, size=self.shape).astype( + self.dtype + ) + self.numpy_tensor[0] = s[0] + elif api_config.api_name == "paddle.nn.functional.alpha_dropout": + if key == "x" or index == 0: + if self.dtype == "bfloat16": + self.dtype = "float32" + self.numpy_tensor = numpy.random.random(self.shape).astype(self.dtype) + + elif api_config.api_name == "paddle.nn.functional.interpolate": + if key == "size" or index == 1 or key == "scale_factor" or index == 2: + self.numpy_tensor = numpy.random.randint(1, 128, size=self.shape).astype( + self.dtype + ) + + elif api_config.api_name == "paddle.nn.functional.gather_tree": + if self.check_arg(api_config, 1, "parents"): + sequences = self.get_arg(api_config, 0, "sequences") + if hasattr(sequences, "shape") and len(sequences.shape) >= 3: + beam_size = sequences.shape[2] + else: + beam_size = self.shape[2] if len(self.shape) >= 3 else 4 + beam_size = 1 if beam_size < 1 else beam_size + parents = numpy.zeros(self.shape, dtype=self.dtype) + for t in range(self.shape[0]): + for b in range(self.shape[1]): + for i in range(self.shape[2]): + parents[t, b, i] = numpy.random.randint(0, beam_size) + self.numpy_tensor = parents + + elif api_config.api_name == "paddle.nn.functional.gaussian_nll_loss": + if self.check_arg(api_config, 2, "var"): + self.numpy_tensor = (numpy.random.random(self.shape) + 1.0).astype(self.dtype) + + elif api_config.api_name == "paddle.nn.functional.hinge_embedding_loss": + if self.check_arg(api_config, 1, "label"): + self.numpy_tensor = numpy.random.randint(0, 2, size=self.shape).astype( + self.dtype + ) + self.numpy_tensor[self.numpy_tensor == 0] = -1 + + elif api_config.api_name == "paddle.nn.functional.hsigmoid_loss": + nclass = self.get_arg(api_config, 2, "num_classes") + weight = self.get_arg(api_config, 3, "weight") + if key == "label" or index == 1: + self.numpy_tensor = numpy.random.randint(0, nclass, size=self.shape).astype( + self.dtype + ) + elif key == "path_table" or index == 5: + self.numpy_tensor = numpy.random.randint( + 0, weight.shape[0], size=self.shape + ).astype(self.dtype) + elif key == "path_code" or index == 6: + self.numpy_tensor = numpy.random.randint(0, 2, size=self.shape).astype( + self.dtype + ) + + elif api_config.api_name == "paddle.nn.functional.upsample": + if self.check_arg(api_config, 1, "size"): + self.numpy_tensor = numpy.random.randint(1, 128, size=self.shape).astype( + self.dtype + ) + if self.check_arg(api_config, 2, "scale_factor"): + self.numpy_tensor = numpy.ones(self.shape).astype(self.dtype) + numpy.abs( + numpy.random.random(self.shape) + ).astype(self.dtype) + + elif api_config.api_name == "paddle.nn.functional.binary_cross_entropy": + self.numpy_tensor = numpy.random.random(self.shape).astype(self.dtype) + + elif api_config.api_name == "paddle.nn.functional.embedding": + if self.check_arg(api_config, 0, "x") or self.check_arg(api_config, 0, "ids"): + weight_config = self.get_arg(api_config, 1, "weight") + if not weight_config: + weight_config = self.get_arg(api_config, None, "weight") + vocab_size = numpy.random.randint(10, 1000) + if isinstance(weight_config, TensorConfig) and weight_config.shape: + vocab_size = weight_config.shape[0] + self.numpy_tensor = numpy.random.randint(0, vocab_size, size=self.shape).astype( + self.dtype + ) + elif self.check_arg(api_config, 1, "weight"): + if self.dtype.startswith("complex"): + real_dtype = "float32" if self.dtype == "complex64" else "float64" + real_part = numpy.random.random(self.shape).astype(real_dtype) + imag_part = numpy.random.random(self.shape).astype(real_dtype) + self.numpy_tensor = (real_part + 1j * imag_part).astype(self.dtype) + else: + self.numpy_tensor = numpy.random.random(self.shape).astype(self.dtype) + + elif api_config.api_name == "paddle.nn.functional.margin_cross_entropy": + if index == 1 or key == "label": + s = self.get_arg(api_config, 0, "logits") + self.numpy_tensor = numpy.random.randint(0, s.shape[1], size=self.shape).astype( + self.dtype + ) + + elif api_config.api_name == "paddle.nn.functional.multi_margin_loss": + if index == 1 or key == "label": + s = self.get_arg(api_config, 0, "input") + self.numpy_tensor = numpy.random.randint(0, s.shape[1], size=self.shape).astype( + self.dtype + ) + + elif api_config.api_name == "paddle.nn.functional.cross_entropy": + if self.check_arg(api_config, 1, "label"): + input_shape = self.get_arg(api_config, 0, "input").shape + axis = self.get_arg(api_config, 7, "axis", -1) + num_classes = self.get_arg(api_config, 0, "input").shape[axis] + soft_label = self.get_arg(api_config, 5, "soft_label", False) + label_smoothing = self.get_arg(api_config, 6, "label_smoothing", 0.0) + if (label_smoothing > 0 and self.shape == input_shape) or ( + label_smoothing == 0 and soft_label + ): + soft_labels = numpy.random.random(size=self.shape) + soft_labels = soft_labels / soft_labels.sum(axis=1, keepdims=True) + self.numpy_tensor = soft_labels.astype(self.dtype) + else: + self.numpy_tensor = numpy.random.randint( + 0, num_classes, size=self.shape + ).astype(self.dtype) + elif self.check_arg(api_config, 3, "weight"): + self.numpy_tensor = numpy.random.random(size=self.shape) + self.numpy_tensor = self.numpy_tensor / self.numpy_tensor.sum() + + elif api_config.api_name == "paddle.nn.functional.ctc_loss": + if self.check_arg(api_config, 1, "labels"): + num_classes = self.get_arg(api_config, 0, "log_probs").shape[2] - 1 + blank = self.get_arg(api_config, 4, "blank", 0) + valid_label_indices = [i for i in range(num_classes + 1) if i != blank] + if not valid_label_indices: + self.numpy_tensor = numpy.zeros(self.shape, dtype=self.dtype) + else: + self.numpy_tensor = numpy.random.choice( + valid_label_indices, size=self.shape, replace=True + ).astype(self.dtype) + elif self.check_arg(api_config, 2, "input_lengths"): + max_logit_length = self.get_arg(api_config, 0, "log_probs").shape[0] + self.numpy_tensor = numpy.random.randint( + 1, max_logit_length + 1, size=self.shape, dtype=self.dtype + ) + elif self.check_arg(api_config, 3, "label_lengths"): + max_label_length = self.get_arg(api_config, 1, "labels").shape[1] + max_logit_length = self.get_arg(api_config, 0, "log_probs").shape[0] + cand_label_lengths = numpy.random.randint( + 1, max_label_length + 1, size=self.shape, dtype=self.dtype + ) + compatible_input_lengths = numpy.random.randint( + 1, max_logit_length + 1, size=self.shape, dtype=self.dtype + ) + final_label_lengths = numpy.minimum( + cand_label_lengths, compatible_input_lengths + ) + final_label_lengths = numpy.maximum(final_label_lengths, 1) + self.numpy_tensor = final_label_lengths + + elif api_config.api_name == "paddle.nn.functional.dice_loss": + if self.check_arg(api_config, 1, "label"): + num_classes = self.get_arg(api_config, 0, "input").shape[-1] + self.numpy_tensor = numpy.random.randint( + 0, num_classes, size=self.shape, dtype=self.dtype + ) + + elif api_config.api_name == "paddle.nn.functional.nll_loss": + if self.check_arg(api_config, 1, "label"): + input_config = self.get_arg(api_config, 0, "input") + n_classes = ( + numpy.random.randint(5, 50) + if not isinstance(input_config, TensorConfig) + else input_config.shape[1] + ) + self.numpy_tensor = numpy.random.randint(0, n_classes, size=self.shape).astype( + self.dtype + ) + + elif api_config.api_name == "paddle.nn.functional.one_hot": + num_classes_config = self.get_arg(api_config, 1, "num_classes") + determined_num_classes = None + default_random_num_classes = numpy.random.randint(1, 65535) + if isinstance(num_classes_config, int): + determined_num_classes = num_classes_config + elif isinstance(num_classes_config, TensorConfig): + if num_classes_config.numpy_tensor is None: + if num_classes_config.numel() == 0 or num_classes_config.numel() == 1: + num_classes_config.numpy_tensor = numpy.array( + [default_random_num_classes], dtype="int64" + ) + determined_num_classes = num_classes_config.numpy_tensor.item() + if self.check_arg(api_config, 0, "x"): + self.numpy_tensor = numpy.random.randint( + 0, determined_num_classes, size=self.shape, dtype=self.dtype + ) + + elif api_config.api_name == "paddle.nn.functional.rnnt_loss": + if self.check_arg(api_config, 0, "logits"): + if len(self.shape) != 4: + self.shape = [3, 4, 3, 5] + self.numpy_tensor = numpy.random.random(self.shape).astype(self.dtype) + elif self.check_arg(api_config, 1, "labels"): + batch_size = 3 + max_label_len = 2 + if len(self.shape) != 2: + self.shape = [batch_size, max_label_len] + vocab_size = 5 + self.numpy_tensor = numpy.random.randint( + 1, vocab_size - 1, size=self.shape + ).astype(self.dtype) + elif self.check_arg(api_config, 2, "input_lengths") or self.check_arg( + api_config, 3, "label_lengths" + ): + batch_size = 3 + if len(self.shape) != 1: + self.shape = [batch_size] + if self.check_arg(api_config, 2, "input_lengths"): + max_possible_length = 4 + self.numpy_tensor = ( + numpy.ones(self.shape, dtype=self.dtype) * max_possible_length + ) + else: + max_possible_length = 2 + self.numpy_tensor = ( + numpy.ones(self.shape, dtype=self.dtype) * max_possible_length + ) + + elif api_config.api_name == "paddle.nn.functional.sequence_mask": + if self.check_arg(api_config, 0, "x"): + maxlen_config = self.get_arg(api_config, 1, "maxlen") + provided_maxlen = None + if isinstance(maxlen_config, int): + provided_maxlen = max(1, maxlen_config) + if provided_maxlen is not None: + self.numpy_tensor = numpy.random.randint( + 0, provided_maxlen + 1, size=self.shape + ).astype(self.dtype) + else: + high_val = numpy.random.randint(1, 2048) + self.numpy_tensor = numpy.random.randint( + 0, high_val, size=self.shape + ).astype(self.dtype) + if self.numpy_tensor.size > 0 and numpy.max(self.numpy_tensor) == 0: + fix_value = numpy.random.randint(1, max(2, high_val)) + first_element_index = numpy.unravel_index(0, self.shape) + self.numpy_tensor[first_element_index] = fix_value + + elif api_config.api_name == "paddle.nn.functional.softmax_with_cross_entropy": + if self.check_arg(api_config, 1, "label"): + logits = None + if len(api_config.args) > 0 and isinstance(api_config.args[0], TensorConfig): + logits = api_config.args[0] + elif "logits" in api_config.kwargs and isinstance( + api_config.kwargs["logits"], TensorConfig + ): + logits = api_config.kwargs["logits"] + num_classes = 10 + if logits is not None: + axis = api_config.kwargs.get("axis", -1) + axis = axis if axis >= 0 else len(logits.shape) + axis + if 0 <= axis < len(logits.shape): + num_classes = logits.shape[axis] + else: + num_classes = numpy.random.randint(5, 20) + self.numpy_tensor = numpy.random.randint( + 0, num_classes, size=self.shape + ).astype(self.dtype) + + elif api_config.api_name == "paddle.normal": + if self.check_arg(api_config, 0, "mean"): + if "int" in self.dtype: + self.numpy_tensor = ( + numpy.random.randint(-65535, 65535, size=self.shape) + ).astype(self.dtype) + else: + self.numpy_tensor = (numpy.random.random(self.shape) - 0.5).astype( + self.dtype + ) + elif self.check_arg(api_config, 1, "std"): + if "int" in self.dtype: + self.numpy_tensor = ( + numpy.random.randint(0, 65535, size=self.shape) + ).astype(self.dtype) + else: + self.numpy_tensor = (numpy.random.random(self.shape)).astype(self.dtype) + else: + self.numpy_tensor = (numpy.random.randint(0, 1024, size=self.shape)).astype( + self.dtype + ) + + elif api_config.api_name == "paddle.ones": + if len(self.shape) == 0: + self.numpy_tensor = numpy.array(numpy.random.randint(1, 2048), dtype=self.dtype) + else: + self.numpy_tensor = numpy.random.randint(1, 65535, size=self.shape).astype( + self.dtype + ) + elif api_config.api_name == "paddle.nn.functional.pad": + if self.check_arg(api_config, 1, "pad"): + x_shape = self.get_arg(api_config, 0, "x").shape + min_dim_len = min(x_shape) + self.numpy_tensor = self.get_random_numpy_tensor( + shape=self.shape, data_type=self.dtype, min=0, max=min_dim_len + ) + elif api_config.api_name == "paddle.nn.functional.class_center_sample": + if self.check_arg(api_config, 0, "label"): + num_classes = self.get_arg(api_config, 1, "num_classes") + self.numpy_tensor = numpy.random.randint( + 0, num_classes, size=self.shape + ).astype(self.dtype) + elif api_config.api_name == "paddle.prod": + if self.check_arg(api_config, 1, "axis"): + self.numpy_tensor = self.generate_random_axes(api_config) + + elif api_config.api_name == "paddle.vision.ops.psroi_pool": + if (index is not None and index == 0) or key == "x": + self.numpy_tensor = ((numpy.random.random(self.shape)) * 255).astype(self.dtype) + if not hasattr(api_config, "x"): + api_config.x = self.shape + elif index == 1 or key == "boxes": + if not hasattr(api_config, "boxes"): + api_config.boxes = self.shape + self.numpy_tensor = numpy.zeros(self.shape).astype(self.dtype) + for i in range(self.shape[0]): + self.numpy_tensor[i][0] = numpy.random.random() * (api_config.x[2] - 2) + self.numpy_tensor[i][1] = numpy.random.random() * (api_config.x[3] - 2) + self.numpy_tensor[i][2] = ( + numpy.random.random() + * (api_config.x[2] - 1 - self.numpy_tensor[i][0] + 1) + + self.numpy_tensor[i][0] + + 1 + ) + self.numpy_tensor[i][3] = ( + numpy.random.random() + * (api_config.x[3] - 1 - self.numpy_tensor[i][1] + 1) + + self.numpy_tensor[i][1] + + 1 + ) + + elif index == 2 or key == "boxes_num": + self.numpy_tensor = numpy.zeros(self.shape).astype(self.dtype) + all = api_config.boxes[0] + for i in range(self.numel() - 1): + if all < self.numel(): + self.numpy_tensor[i] = 0 + else: + self.numpy_tensor[i] = numpy.random.randint( + 1, all - (self.numel() - 1 - i) + 1 + ) + all = all - self.numpy_tensor[i] + self.numpy_tensor[self.numel() - 1] = all + else: + self.numpy_tensor = numpy.random.randint(0, 1024, self.shape).astype(self.dtype) + + elif api_config.api_name in { + "paddle.put_along_axis", + "paddle.Tensor.put_along_axis", + "paddle.put_along_axis_", + "paddle.Tensor.put_along_axis_", + "paddle._C_ops.put_along_axis", + "paddle._C_ops.Tensor.put_along_axis", + "paddle._C_ops.put_along_axis_", + "paddle._C_ops.Tensor.put_along_axis_", + }: + if self.check_arg(api_config, 1, "indices"): + x_tensor = self.get_arg(api_config, 0, "x") + x_dims = len(x_tensor.shape) if x_tensor.shape else 0 + if len(self.shape) != x_dims: + new_shape = [] + for i, dim in enumerate(x_tensor.shape): + if i < len(self.shape): + new_shape.append(self.shape[i]) + else: + new_shape.append(1) + indices = numpy.zeros(new_shape, dtype="int64") + for axis in range(x_dims): + if axis < len(self.shape): + dim_size = x_tensor.shape[axis] + if dim_size > 0: + axis_indices = numpy.random.choice( + dim_size, size=new_shape[axis], replace=False + ).astype("int64") + idx_tuple = tuple( + [slice(None)] * axis + + [slice(None, new_shape[axis])] + + [slice(None)] * (x_dims - axis - 1) + ) + indices[idx_tuple] = axis_indices.reshape( + [-1] + [1] * (x_dims - axis - 1) + ) + self.numpy_tensor = indices + self.shape = new_shape + else: + axis = self.get_arg(api_config, 3, "axis") + axis = axis if isinstance(axis, int) else 0 + axis = axis if axis >= 0 else axis + x_dims + if 0 <= axis < x_dims: + dim_size = x_tensor.shape[axis] + indices = numpy.zeros(self.shape, dtype="int64") + for idx in numpy.ndindex(tuple(self.shape[:-1])): + indices[idx] = numpy.random.choice( + dim_size, size=self.shape[-1], replace=False + ) + self.numpy_tensor = indices + self.dtype = "int64" + elif self.check_arg(api_config, 2, "values"): + x_tensor = self.get_arg(api_config, 0, "x") + indices = self.get_arg(api_config, 1, "indices") + if hasattr(indices, "shape"): + if indices.shape != self.shape: + if numpy.prod(self.shape) == 1: + self.numpy_tensor = numpy.full( + indices.shape, + self.get_random_numpy_tensor(shape=[], data_type=self.dtype)[ + () + ], + dtype=self.dtype, + ) + else: + random_values = self.get_random_numpy_tensor( + shape=numpy.prod(indices.shape), + data_type=self.dtype, + ) + self.numpy_tensor = random_values.reshape(indices.shape) + else: + self.numpy_tensor = self.get_random_numpy_tensor( + shape=self.shape, data_type=self.dtype + ) + elif api_config.api_name == "paddle.quantile": + if not (key == "x" or index == 0): + self.numpy_tensor = numpy.random.rand(1).astype(self.dtype) + elif api_config.api_name in {"paddle.Tensor.reshape", "paddle.reshape"}: + if index == 0 or key == "x": + if 0 not in self.shape: + if not hasattr(api_config, "shape"): + api_config.shape = self.shape + if not hasattr(api_config, "maxvalue"): + api_config.maxvalue = self.numel() + if not hasattr(api_config, "tensornum"): + api_config.tensornum = 0 + for arg in api_config.args: + if isinstance(arg, (list, tuple)): + i = 0 + for item in arg: + if "int" in str(type(item)): + if item == 0: + api_config.maxvalue = ( + api_config.maxvalue // self.shape[i] + ) + elif item != -1: + api_config.maxvalue = api_config.maxvalue // item + if "Tensor" in str(type(item)): + api_config.tensornum += 1 + i += 1 + for _thekey, thevalue in api_config.kwargs.items(): + if isinstance(thevalue, (list, tuple)): + i = 0 + for item in thevalue: + if "int" in str(type(item)): + if item == 0: + api_config.maxvalue = ( + api_config.maxvalue // self.shape[i] + ) + elif item != -1: + api_config.maxvalue = api_config.maxvalue // item + if "Tensor" in str(type(item)): + api_config.tensornum += 1 + i += 1 + else: + if api_config.tensornum == 0: + api_config.tensornum = 1 + self.dtype = "int32" + if self.shape != [] and self.shape != [1]: + self.numpy_tensor = numpy.zeros(self.shape).astype(self.dtype) + for i in range(self.shape[0]): + if i < self.shape[0] - 1: + self.numpy_tensor[i] = numpy.random.randint( + 1, api_config.maxvalue + 1 + ) + while api_config.maxvalue % self.numpy_tensor[i]: + self.numpy_tensor[i] = numpy.random.randint( + 1, api_config.maxvalue + 1 + ) + api_config.maxvalue = api_config.maxvalue // self.numpy_tensor[i] + else: + self.numpy_tensor[i] = api_config.maxvalue + else: + if api_config.tensornum == 1: + self.numpy_tensor = numpy.random.randint( + api_config.maxvalue, + api_config.maxvalue + 1, + size=self.shape, + ).astype(self.dtype) + else: + api_config.tensornum -= 1 + self.numpy_tensor = numpy.random.randint( + 1, api_config.maxvalue + 1, size=self.shape + ).astype(self.dtype) + while api_config.maxvalue % self.numpy_tensor: + self.numpy_tensor = numpy.random.randint( + 1, api_config.maxvalue + 1, size=self.shape + ).astype(self.dtype) + api_config.maxvalue = api_config.maxvalue // self.numpy_tensor + + elif api_config.api_name in { + "paddle.vision.ops.roi_align", + "paddle.vision.ops.roi_pool", + }: + if (index is not None and index == 0) or key == "x": + self.numpy_tensor = ((numpy.random.random(self.shape)) * 255).astype(self.dtype) + if not hasattr(api_config, "x"): + api_config.x = self.shape + elif (index is not None and index == 1) or key == "boxes": + if not hasattr(api_config, "boxes"): + api_config.boxes = self.shape + self.numpy_tensor = numpy.zeros(self.shape).astype(self.dtype) + for i in range(self.shape[0]): + self.numpy_tensor[i][0] = numpy.random.random() * (api_config.x[2] - 2) + self.numpy_tensor[i][1] = numpy.random.random() * (api_config.x[3] - 2) + self.numpy_tensor[i][2] = ( + numpy.random.random() + * (api_config.x[2] - 1 - self.numpy_tensor[i][0] + 1) + + self.numpy_tensor[i][0] + + 1 + ) + self.numpy_tensor[i][3] = ( + numpy.random.random() + * (api_config.x[3] - 1 - self.numpy_tensor[i][1] + 1) + + self.numpy_tensor[i][1] + + 1 + ) + elif index == 2 or key == "boxes_num": + self.numpy_tensor = numpy.zeros(self.shape).astype(self.dtype) + all = api_config.boxes[0] + for i in range(self.numel() - 1): + if all < self.numel(): + self.numpy_tensor[i] = 0 + else: + self.numpy_tensor[i] = numpy.random.randint( + 1, all - (self.numel() - 1 - i) + 1 + ) + all = all - self.numpy_tensor[i] + self.numpy_tensor[self.numel() - 1] = all + + elif api_config.api_name.find(".repeat_interleave") > 0: + if self.check_arg(api_config, 0, "x"): + if self.dtype == "bfloat16": + self.dtype = "float32" + elif self.check_arg(api_config, 1, "repeats"): + self.numpy_tensor = numpy.random.randint(1, 2048, size=self.shape).astype( + self.dtype + ) + elif self.check_arg(api_config, 2, "axis"): + x_tensor = self.get_arg(api_config, 0, "x") + input_dims = len(x_tensor.shape) + if len(self.shape) == 0: + self.numpy_tensor = numpy.array( + numpy.random.randint(-input_dims, input_dims), + dtype=self.dtype, + ) + else: + self.numpy_tensor = numpy.random.randint( + -input_dims, input_dims, size=self.shape + ).astype(self.dtype) + elif api_config.api_name == "paddle.slice": + # if not hasattr(api_config, "element1"): + # if "axes" in api_config.kwargs: + # lens = len(api_config.kwargs["axes"]) + # else: + # lens = len(api_config.args[1]) + # api_config.element1 = lens + 1 + # if not hasattr(api_config, "element2"): + # if "starts" in api_config.kwargs: + # item = api_config.kwargs["starts"] + # else: + # item = api_config.args[2] + # if isinstance(item, list): + # api_config.element2 = api_config.element1 + len(item) + # else: + # api_config.element2 = api_config.element1 + 1 + axis = api_config.args[1] if len(api_config.args) > 1 else api_config.kwargs["axes"] + if (index is not None and index == 0) or key == "input": + if not hasattr(api_config, "shape"): + api_config.shape = self.shape + elif (index is not None and index == 2) or key == "starts": + num = [] + for i in axis: + num.append(api_config.shape[i]) + if not hasattr(api_config, "indice"): + api_config.indice = 0 + if not hasattr(api_config, "start"): + api_config.start = [] + if self.shape == []: + x = numpy.random.randint(0, 2) + if x == 0: + self.numpy_tensor = numpy.random.randint( + 0, num[api_config.indice] - 1, self.shape + ) + else: + self.numpy_tensor = numpy.random.randint(-65535, -1, self.shape) + api_config.start.append(self.numpy_tensor) + api_config.indice += 1 + else: + self.numpy_tensor = numpy.zeros(self.shape).astype(self.dtype) + for i in range(self.numel()): + x = numpy.random.randint(0, 2) + if x == 0: + self.numpy_tensor[i] = numpy.random.randint( + 0, num[api_config.indice] - 1 + ) + else: + self.numpy_tensor[i] = numpy.random.randint(-65535, -1) + api_config.start.append(self.numpy_tensor[i]) + api_config.indice += 1 + else: + if not hasattr(api_config, "start"): + if len(api_config.args) > 2: + api_config.start = api_config.args[2] + else: + api_config.start = api_config.kwargs["starts"] + num = [] + for i in axis: + num.append(api_config.shape[i]) + start = api_config.start + for i in range(len(start)): + if start[i] < 0: + start[i] = start[i] if start[i] > -1 * num[i] else -1 * num[i] + start[i] += num[i] + if not hasattr(api_config, "index"): + api_config.index = 0 + if self.shape == []: + x = numpy.random.randint(0, 2) + if x == 0: + self.numpy_tensor = numpy.random.randint( + start[api_config.index] + 1, 65535, self.shape + ) + else: + if start[api_config.index] - num[i] == 0: + start[api_config.index] -= 1 + self.numpy_tensor = numpy.random.randint( + min(start[api_config.index] - num[i] + 1, -1), + 0, + self.shape, + ) + api_config.index += 1 + else: + self.numpy_tensor = numpy.zeros(self.shape).astype(self.dtype) + for i in range(self.numel()): + x = numpy.random.randint(0, 2) + if x == 0: + self.numpy_tensor[i] = numpy.random.randint( + start[api_config.index] + 1, 65535 + ) + else: + if start[api_config.index] - num[i] == 0: + start[api_config.index] -= 1 + self.numpy_tensor[i] = numpy.random.randint( + start[api_config.index] - num[api_config.index] + 1, + 0, + ) + api_config.index += 1 + + elif api_config.api_name == "paddle.scatter": + if key == "index" or index == 1: + d = self.get_arg(api_config, 0, "x") + s = d.shape[0] + overwrite = self.get_arg(api_config, 3, "overwrite") + if (overwrite == None or overwrite == True) and ( + self.shape == [] or self.shape[0] + ) <= s: + self.numpy_tensor = numpy.random.choice( + s, size=self.shape, replace=False + ).astype(self.dtype) + else: + self.numpy_tensor = numpy.random.randint(0, s, size=self.shape).astype( + self.dtype + ) + + elif api_config.api_name == "paddle.scatter_nd": + future_data = self.get_arg(api_config, 2, "shape") + if (key == "index" or index == 0) and future_data and len(future_data): + self.numpy_tensor = numpy.zeros(self.shape) + s = self.shape + for ii in range(len(future_data)): + if ii >= s[-1]: + break + self.numpy_tensor[..., ii] = numpy.random.randint( + -future_data[ii], + future_data[ii], + size=self.numpy_tensor[..., ii].shape, + ).astype(self.dtype) + + elif api_config.api_name == "paddle.scatter_nd_add": + if key == "index" or index == 1: + org = self.get_arg(api_config, 0, "x") + org = org.shape + self.numpy_tensor = numpy.zeros(self.shape) + ind = self.get_arg(api_config, 1, "index") + s = ind.shape + for ii in range(s[-1]): + self.numpy_tensor[..., ii] = numpy.random.randint( + -org[ii], org[ii], size=self.numpy_tensor[..., ii].shape + ).astype(self.dtype) + elif api_config.api_name == "paddle.shard_index": + if self.check_arg(api_config, 0, "input"): + index_num = self.get_arg(api_config, 1, "index_num") + if index_num is None: + index_num = numpy.random.randint(1, 1000) + self.numpy_tensor = numpy.random.randint(0, index_num, size=self.shape).astype( + self.dtype + ) + elif api_config.api_name in {"paddle.sum", "paddle.squeeze"}: + if self.check_arg(api_config, 1, "axis"): + self.numpy_tensor = self.generate_random_axes(api_config) + elif api_config.api_name == "paddle.split": + if self.check_arg(api_config, 2, "axis"): + x_shape = self.get_arg(api_config, 0, "x").shape + num_or_sections = self.get_arg(api_config, 1, "num_or_sections") + if isinstance(num_or_sections, (list, tuple)): + neg_one_count = sum(1 for x in num_or_sections if x == -1) + if neg_one_count > 1: + raise ValueError( + f"num_or_sections can contain at most one -1, but got {num_or_sections}" + ) + num_splits = len(num_or_sections) + known_size = sum(num_or_sections) + neg_one_count + elif isinstance(num_or_sections, int): + num_splits = num_or_sections + known_size = None + else: + raise ValueError( + f"num_or_sections must be an int, list, or tuple, but got {type(num_or_sections)}" + ) + + target_dim = None + max_dim = len(x_shape) + if max_dim == 0: + target_dim = numpy.random.randint(-1, 0) + else: + for dim in range(max_dim): + dim_size = x_shape[dim] + if isinstance(num_or_sections, int) and dim_size % num_splits == 0: + target_dim = dim + elif isinstance(num_or_sections, (list, tuple)): + if (neg_one_count == 0 and dim_size == known_size) or ( + neg_one_count == 1 and dim_size > known_size + ): + target_dim = dim + if target_dim is None: + raise ValueError( + f"No valid axis found for paddle.split with x.shape={x_shape} and num_or_sections={num_or_sections}" + ) + + shape_len = len(self.shape) + if shape_len == 0: + self.numpy_tensor = numpy.array(target_dim, dtype=self.dtype) + elif shape_len == 1 and self.shape[0] == 1: + self.numpy_tensor = numpy.array([target_dim], dtype=self.dtype) + else: + raise ValueError( + f"Invalid shape for 'axis' Tensor in paddle.split. " + f"Expected a 0-D or 1-D Tensor, but got shape {self.shape}." + ) + + elif api_config.api_name == "paddle.nn.functional.softmax": + # for TensorConfig axis + x_tensor_config = self.get_arg(api_config, 0, "x") + axis_config = self.get_arg(api_config, 1, "axis") + + if self.check_arg(api_config, 0, "x"): + self.numpy_tensor = self.get_random_numpy_tensor(self.shape, self.dtype) + elif self.check_arg(api_config, 1, "axis"): + len_shape_x = len(x_tensor_config.shape) + # specify if axis is a scalar tensor, else is a int according to doc + if isinstance(axis_config, TensorConfig): + axis = self.get_random_numpy_tensor( + axis_config.shape, + axis_config.dtype, + min=-len_shape_x, + max=len_shape_x, + ) + self.numpy_tensor = axis + + elif api_config.api_name == "paddle.standard_gamma": + self.numpy_tensor = numpy.random.random(self.shape).astype(self.dtype) + + elif api_config.api_name == "paddle.standard_normal": + if index == 0 or key == "shape": + self.numpy_tensor = numpy.random.randint(1, 128, size=self.shape).astype( + self.dtype + ) + + elif api_config.api_name == "paddle.strided_slice": + s = self.get_arg(api_config, 0, "x") + if self.check_arg(api_config, 1, "axes"): + self.numpy_tensor = numpy.random.randint( + 0, len(s.shape), size=self.shape + ).astype(self.dtype) + elif index: + axes = self.get_arg(api_config, 1, "axes") + for i in range(len(axes)): + if isinstance(axes[i], TensorConfig): + axes[i] = int(axes[i].numpy_tensor) + if self.check_arg(api_config, 2, "starts"): + axes = self.get_arg(api_config, 1, "axes") + if not isinstance(axes, list): + axes = axes.numpy_tensor + ind = kwargs["list_index"][0] + self.numpy_tensor = numpy.random.randint( + 0, s.shape[axes[ind]] - 1, size=self.shape + ).astype(self.dtype) + elif self.check_arg(api_config, 3, "ends"): + ind = kwargs["list_index"][0] + pre = self.get_arg(api_config, 2, "starts") + self.numpy_tensor = numpy.random.randint( + pre[ind].numpy_tensor + 1, + s.shape[axes[ind]], + size=self.shape, + ).astype(self.dtype) + elif self.check_arg(api_config, 4, "strides"): + ind = kwargs["list_index"][0] + self.numpy_tensor = numpy.random.randint( + 1, s.shape[axes[ind]], size=self.shape + ).astype(self.dtype) + elif api_config.api_name == "paddle.tensordot": + if index == 0 or key == "x": + if not hasattr(api_config, "shape1"): + api_config.shape1 = self.shape + elif index == 1 or key == "y": + if not hasattr(api_config, "shape2"): + api_config.shape2 = self.shape + else: + item = self.get_arg(api_config, 2, "axes") + num = len(api_config.shape1) + used = [] + if isinstance(item, (list, tuple)): + if not hasattr(api_config, "tensor1"): + self.numpy_tensor = numpy.zeros(self.shape).astype(self.dtype) + for i in range(self.numel()): + self.numpy_tensor[i] = numpy.random.randint(0, num) + while ( + api_config.shape1[self.numpy_tensor[i]] not in api_config.shape2 + or self.numpy_tensor[i] in used + ): + self.numpy_tensor[i] = numpy.random.randint(0, num) + used.append(self.numpy_tensor[i]) + api_config.tensor1 = self.numpy_tensor + else: + self.numpy_tensor = numpy.zeros(self.shape).astype(self.dtype) + for i in range(self.numel()): + self.numpy_tensor[i] = numpy.random.randint(0, num) + while ( + api_config.shape2[self.numpy_tensor[i]] + != api_config.shape1[api_config.tensor1[i]] + or self.numpy_tensor[i] in used + ): + self.numpy_tensor[i] = numpy.random.randint(0, num) + used.append(self.numpy_tensor[i]) + + elif isinstance(item, TensorConfig): + self.tensor = numpy.random.randint(0, 2, size=self.shape).astype(self.dtype) + if self.numel() == 1: + self.numpy_tensor = numpy.random.randint(0, num, self.shape).astype( + self.dtype + ) + dim1 = len(api_config.shape1) + dim2 = len(api_config.shape2) + min_dim = min(dim1, dim2) + candidate_set = set() + for i in range(min_dim): + if api_config.shape1[i] == api_config.shape2[i]: + candidate_set.add(i) + if candidate_set: + import random + + self.numpy_tensor = [random.choice(list(candidate_set))] + else: + raise ValueError( + f"No valid axis found for tensordot,x shape {api_config.shape1}, y shape {api_config.shape2},axes {item}" + ) + else: + used1 = [] + used2 = [] + self.numpy_tensor = numpy.zeros(self.shape).astype(self.dtype) + for i in range(self.shape[0]): + self.numpy_tensor[0][i] = numpy.random.randint(0, num) + self.numpy_tensor[1][i] = numpy.random.randint(0, num) + while ( + api_config.shape1[self.numpy_tensor[0][i]] + != api_config.shape2[self.numpy_tensor[1][i]] + or self.numpy_tensor[0][i] in used1 + or self.numpy_tensor[1][i] in used2 + ): + self.numpy_tensor[0][i] = numpy.random.randint(0, num) + self.numpy_tensor[1][i] = numpy.random.randint(0, num) + used1.append(self.numpy_tensor[0][i]) + used2.append(self.numpy_tensor[1][i]) + + elif api_config.api_name in { + "paddle.Tensor.take_along_axis", + "paddle.take_along_axis", + }: + if self.check_arg(api_config, 1, "indices"): + arr_config = self.get_arg(api_config, 0, "arr") + axis = self.get_arg(api_config, 2, "axis") + arr_shape = arr_config.shape + arr_rank = len(arr_shape) + axis_val = axis if axis >= 0 else axis + arr_rank + dim_size = arr_shape[axis_val] + if self.dtype not in ["int32", "int64"]: + self.dtype = "int64" + num_elements = self.numel() + if num_elements == 0: + indices = numpy.array([], dtype=self.dtype) + elif dim_size == 1: + indices = numpy.zeros(num_elements, dtype=self.dtype) + elif num_elements == 1: + indices = numpy.array([0], dtype=self.dtype) + else: + indices = numpy.random.randint(0, dim_size, size=num_elements).astype( + self.dtype + ) + positions_to_replace = numpy.random.choice( + num_elements, size=2, replace=False + ) + flat_indices = indices.flatten() + flat_indices[positions_to_replace[0]] = 0 + flat_indices[positions_to_replace[1]] = dim_size - 1 + indices = flat_indices + self.numpy_tensor = indices.reshape(self.shape) + + elif api_config.api_name == "paddle.take": + if self.check_arg(api_config, 1, "index"): + x = self.get_arg(api_config, 0, "x") + dim_size = numpy.prod(x.shape) + self.numpy_tensor = numpy.random.randint(0, dim_size, size=self.shape).astype( + self.dtype + ) + + elif api_config.api_name in {"paddle.Tensor.gather", "paddle.gather"}: + if key == "index" or index == 1: + s = self.get_arg(api_config, 0, "x") + if "axis" in api_config.kwargs: + tmp = self.get_arg(api_config, 2, "axis") + if isinstance(tmp, TensorConfig): + tmp = tmp.shape + tmp = tmp[0] + else: + tmp = 0 + self.numpy_tensor = ( + numpy.random.randint(0, s.shape[tmp], size=self.shape) + ).astype(self.dtype) + elif key == "axis" or index == 2: + self.numpy_tensor = (numpy.random.randint(0, 2, size=self.shape)).astype( + self.dtype + ) + + elif api_config.api_name in {"paddle.Tensor.gather_nd", "paddle.gather_nd"}: + if key == "index" or index == 1: + org = self.get_arg(api_config, 0, "x") + org = org.shape + s = self.get_arg(api_config, 1, "index") + s = s.shape + self.numpy_tensor = numpy.zeros(s) + for i in range(s[-1]): + self.numpy_tensor[..., i] = ( + numpy.random.randint(0, org[i], size=self.numpy_tensor[..., i].shape) + ).astype(self.dtype) + + elif api_config.api_name in { + "paddle.Tensor.index_select", + "paddle.index_select", + }: + if self.check_arg(api_config, 1, "index") or self.check_arg(api_config, 2, "index"): + axis = self.get_arg(api_config, 2, "axis") + if axis is None: + axis = 0 + inputs = self.get_arg(api_config, 0, "x") + self.numpy_tensor = numpy.random.randint( + 0, inputs.shape[axis], size=self.shape + ).astype(self.dtype) + + elif api_config.api_name in {"paddle.Tensor.index_put", "paddle.index_put"}: + if self.check_arg(api_config, 1, "indices") and not self.get_arg( + api_config, 3, "accumulate" + ): + # NOTE(zrr1999): If accumulate is False, the behavior is undefined if indices contain duplicate elements in torch. + + inputs = self.get_arg(api_config, 0, "x") + value = self.get_arg(api_config, 2, "value") + inputs_numel = inputs.numel() + value_numel = value.numel() + if inputs_numel < value_numel: + raise ValueError( + "Invalid input for paddle.index_put: inputs.numel() < value.numel() when accumulate=False. " + ) + inputs_shape = inputs.shape + value_shape = value.shape + + flat_indices = numpy.random.choice( + inputs_numel, size=value_numel, replace=False + ) + indices = [ + index.reshape(value_shape) + for index in numpy.unravel_index(flat_indices, inputs_shape) + ] + self.numpy_tensor = indices.astype(self.dtype) + + elif api_config.api_name == "paddle.Tensor.tile": + if index == 1 or key == "repeat_times": + self.numpy_tensor = numpy.random.randint(1, 128, size=self.shape).astype( + self.dtype + ) + + elif api_config.api_name == "paddle.tile": + if self.check_arg(api_config, 1, "repeat_times"): + self.numpy_tensor = numpy.random.randint(1, 128, size=self.shape).astype( + self.dtype + ) + + elif api_config.api_name in {"paddle.topk", "paddle.Tensor.topk"}: + if self.check_arg(api_config, 0, "x"): + x_numel = self.numel() + if self.dtype in {"bfloat16", "float32", "float64"}: + self.numpy_tensor = ((numpy.random.random(self.shape) - 0.5) * 1.2).astype( + self.dtype + ) + elif self.dtype == "float16": + self.numpy_tensor = ( + numpy.random.randn(*self.shape).astype(self.dtype) * 1e-3 + ) + elif self.dtype in {"int32", "int64"}: + self.numpy_tensor = (numpy.random.randint(-10, 10, size=self.shape)).astype( + self.dtype + ) + else: + raise ValueError( + f"Unsupported dtype {self.dtype} for paddle.topk / paddle.Tensor.topk" + ) + elif self.check_arg(api_config, 1, "k"): + x_config = self.get_arg(api_config, 0, "x") + axis = self.get_arg(api_config, 2, "axis", -1) + max_k_value = 1 + if isinstance(x_config, TensorConfig) and x_config.shape: + max_k_value = x_config.shape[axis] if len(x_config.shape) > 0 else 1 + if not self.shape: + self.numpy_tensor = numpy.array( + numpy.random.randint(1, max_k_value + 1), dtype=self.dtype + ) + else: + self.numpy_tensor = numpy.random.randint( + 1, max_k_value + 1, size=self.shape + ).astype(self.dtype) + elif api_config.api_name in {"paddle.Tensor.unflatten", "paddle.unflatten"}: + if self.check_arg(api_config, 1, "axis"): + x_shape = self.get_arg(api_config, 0, "x").shape + self.numpy_tensor = numpy.random.randint( + 0, len(x_shape), size=self.shape + ).astype(self.dtype) + elif self.check_arg(api_config, 2, "shape"): + axis = self.get_arg(api_config, 1, "axis") + x_dim = self.get_arg(api_config, 0, "x").shape[axis] + shape = self.get_arg(api_config, 2, "shape") + if isinstance(shape, TensorConfig): + self.numpy_tensor = numpy.ones(self.shape).astype(self.dtype) + remaining = x_dim + for i in range(shape.numel() - 1): + if remaining <= 1: + break + divisors = [d for d in range(2, remaining + 1) if remaining % d == 0] + if divisors: + divisor = numpy.random.choice(divisors) + self.numpy_tensor[i] = divisor + remaining = remaining // divisor + self.numpy_tensor[-1] = remaining + elif isinstance(shape, (list, tuple)): + tensor_configs = [item for item in shape if isinstance(item, TensorConfig)] + tensornum = len(tensor_configs) + if tensornum > 0: + remaining = x_dim + fixed_product = 1 + for dim in shape: + if isinstance(dim, int) and dim != -1: + fixed_product *= dim + if fixed_product > 0 and x_dim % fixed_product == 0: + remaining = x_dim // fixed_product + for i in range(tensornum - 1): + if remaining <= 1: + break + divisors = [ + d for d in range(2, remaining + 1) if remaining % d == 0 + ] + if divisors: + divisor = numpy.random.choice(divisors) + tensor_config = tensor_configs[i] + tensor_config.numpy_tensor = numpy.full( + tensor_config.shape, + divisor, + dtype=tensor_config.dtype, + ) + remaining = remaining // divisor + tensor_config = tensor_configs[-1] + tensor_config.numpy_tensor = numpy.full( + tensor_config.shape, + remaining, + dtype=tensor_config.dtype, + ) + + elif api_config.api_name == "paddle.unsqueeze": + if self.check_arg(api_config, 1, "axis"): + x_shape = self.get_arg(api_config, 0, "x").shape + max_dim = len(x_shape) + 1 + if len(self.shape) == 0: + dim = numpy.random.randint(0, max_dim) + if numpy.random.rand() > 0.5: + dim -= max_dim + self.numpy_tensor = numpy.array(dim, dtype=self.dtype) + elif len(self.shape) == 1: + dims = numpy.random.choice(max_dim, size=self.shape[0], replace=False) + mask = numpy.random.rand(self.shape[0]) > 0.5 + dims = numpy.where(mask, dims - max_dim, dims) + self.numpy_tensor = numpy.array(dims, dtype=self.dtype) + else: + raise ValueError( + f"Invalid shape for 'axis' Tensor in paddle.unsqueeze. " + f"Expected a 0-D or 1-D Tensor, but got shape {self.shape}." + ) + elif ( + api_config.api_name + == "paddle.incubate.nn.functional.variable_length_memory_efficient_attention" + ): + if self.check_arg(api_config, 3, "seq_lens"): + q_seq_len = self.get_arg(api_config, 0, "query").shape[2] + self.numpy_tensor = self.get_random_numpy_tensor( + shape=self.shape, data_type=self.dtype, min=1, max=q_seq_len + ) + elif self.check_arg(api_config, 4, "kv_seq_lens"): + k_seq_len = self.get_arg(api_config, 1, "key").shape[2] + v_seq_len = self.get_arg(api_config, 2, "value").shape[2] + self.numpy_tensor = self.get_random_numpy_tensor( + shape=self.shape, + data_type=self.dtype, + min=1, + max=min(k_seq_len, v_seq_len), + ) + elif self.check_arg(api_config, 5, "mask"): + # mask should between -inf and 0 (0 is included) + # eps = numpy.finfo(self.dtype).eps + # self.numpy_tensor = self.get_random_numpy_tensor(shape=self.shape, data_type=self.dtype, max=0 + eps) + # mask should be -inf(masked) or 0(not masked) + self.numpy_tensor = numpy.random.randint(0, 2, size=self.shape).astype( + self.dtype + ) * (numpy.finfo(self.dtype).min) + elif api_config.api_name == "paddle.zeros": + self.numpy_tensor = numpy.random.randint(0, 2048, size=self.shape) + + elif api_config.api_name == "paddle.nn.functional.zeropad2d": + if self.check_arg(api_config, 0, "x"): + self.numpy_tensor = self.get_random_numpy_tensor(self.shape, self.dtype) + elif self.check_arg(api_config, 1, "padding"): + # padding value should not be too large + self.numpy_tensor = self.get_random_numpy_tensor( + self.shape, self.dtype, min=0, max=10 + ) + + elif api_config.api_name == "paddle.Tensor.__getitem__": + if self.check_arg(api_config, 1, "item"): + arr = self.get_arg(api_config, 0, "arr") + min_dim = min(arr.shape) + if self.dtype == "bool": + indices = numpy.random.choice([0, 1], size=self.numel()) + else: + indices = numpy.random.randint(0, min_dim, size=self.numel()) + self.numpy_tensor = indices.reshape(self.shape).astype(self.dtype) + + elif api_config.api_name == "paddle.Tensor.__setitem__": + if self.check_arg(api_config, 1, "item"): + arr = self.get_arg(api_config, 0, "arr") + value = self.get_arg(api_config, 2, "value") + min_dim = min(arr.shape) + if self.dtype == "bool": + if value is not None and hasattr(value, "shape"): + indices = numpy.zeros(self.numel(), dtype="int64") + num_true = min(value.shape[0], self.numel()) + true_indices = numpy.random.choice( + self.numel(), size=num_true, replace=False + ) + indices[true_indices] = 1 + else: + indices = numpy.random.choice([0, 1], size=self.numel()) + else: + indices = numpy.random.randint(0, min_dim, size=self.numel()) + self.numpy_tensor = indices.reshape(self.shape).astype(self.dtype) + + elif api_config.api_name == "paddle.poisson": + self.numpy_tensor = numpy.random.random(self.shape).astype(self.dtype) + + elif api_config.api_name in { + "paddle.Tensor.__pow__", + "paddle.Tensor.pow", + "paddle.pow", + "paddle.Tensor.__rpow__", + }: + dtype = self.dtype + + def get_base_max(value, dtype_max, default_max=5): + value_max = default_max + if value <= 0: + return value_max + if value < 1: + # value**(-max) < MAX => (1/value)**max < MAX + value = 1 / value + ln_value = math.log(value) + # dy/dx = y*ln(value) < MAX, y < MAX => y*max(ln(value), 1) < MAX + output_max = dtype_max / max(1, ln_value) + value_max = math.log(output_max) / ln_value + if isinstance(value, int): + value_max = math.floor(value_max) + return value_max + + def get_exponent_max(value, dtype_max, default_max=5): + value_max = default_max + if isinstance(value, (int, float, bool, numpy.number)): + if value <= 2: + return value_max + value_max = math.pow(dtype_max / value, 1 / value) + if isinstance(value, int): + value_max = math.floor(value_max) + return value_max + + if api_config.api_name == "paddle.Tensor.__rpow__": + # paddle.Tensor.__rpow__(a, b) => b ^ a, where a is self and b is other + is_base_arg = self.check_arg(api_config, 1, "other") + if is_base_arg: + const = self.get_arg(api_config, 0, "self") + get_max = get_base_max + default_max = 10 + else: + const = self.get_arg(api_config, 1, "other") + get_max = get_exponent_max + default_max = 5 + else: + # paddle.Tensor.__pow__(a, b) => a ^ b, where a is self and b is other + is_base_arg = self.check_arg(api_config, 0, "self") or self.check_arg( + api_config, 0, "x" + ) + if is_base_arg: + const = self.get_arg(api_config, 1, "other") + get_max = get_base_max + default_max = 10 + else: + const = self.get_arg(api_config, 0, "self") + get_max = get_exponent_max + default_max = 5 + if isinstance(const, (int, float, bool, numpy.number)): + value_max = get_max(const, numpy.finfo(self.dtype).max, default_max) + if is_base_arg and int(const) != const: + # Avoid situations like (-2.3) ^ 0.5 + self.numpy_tensor = self.get_random_numpy_tensor( + self.shape, self.dtype, min=0, max=value_max + ) + else: + self.numpy_tensor = self.get_random_numpy_tensor( + self.shape, self.dtype, min=-value_max, max=value_max + ) + else: + if is_base_arg: + # Avoid situations like (-2.3) ^ 0.5 + self.numpy_tensor = self.get_random_numpy_tensor( + self.shape, self.dtype, min=0, max=default_max + ) + else: + self.numpy_tensor = self.get_random_numpy_tensor( + self.shape, self.dtype, min=-default_max, max=default_max + ) + elif api_config.api_name == "paddle.nn.functional.sigmoid_focal_loss": + if self.check_arg(api_config, 1, "label"): + self.numpy_tensor = numpy.random.randint(low=0, high=2, size=self.shape).astype( + self.dtype + ) + + elif api_config.api_name.endswith("cholesky_solve"): + if self.check_arg(api_config, 1, "y"): + is_upper = self.get_arg(api_config, 2, "upper") + if is_upper: + self.numpy_tensor = numpy.triu( + self.get_random_numpy_tensor(self.shape, self.dtype) + ) + else: + self.numpy_tensor = numpy.tril( + self.get_random_numpy_tensor(self.shape, self.dtype) + ) + elif api_config.api_name in {"paddle.sqrt", "paddle.Tensor.sqrt"}: + if self.check_arg(api_config, 0, "x"): + self.numpy_tensor = self.get_random_numpy_tensor( + self.shape, self.dtype, min=0, max=1000 + ) + elif api_config.api_name in {"paddle.rsqrt", "paddle.Tensor.rsqrt"}: + if self.check_arg(api_config, 0, "x"): + self.numpy_tensor = self.get_random_numpy_tensor( + self.shape, self.dtype, min=1e-7, max=1000 + ) + elif api_config.api_name in {"paddle.remainder", "paddle.Tensor.remainder"}: + if self.check_arg(api_config, 1, "y"): + if self.dtype in {"int32", "int64"}: + self.numpy_tensor = self.get_random_numpy_tensor( + self.shape, self.dtype, min=1 + ) + + elif api_config.api_name in { + "paddle.nn.functional.moe_permute", + "paddle.nn.functional.moe_unpermute", + }: + if api_config.api_name == "paddle.nn.functional.moe_permute": + # moe_permute(hidden_states, scale, expert_routemap_topk, expert_prob_topk, + # num_experts, tokens_per_expert, padding_alignment, ...) + # expert_routemap_topk (arg2): int32, shape [seqlen, topk], value in [-1, num_experts) + if self.check_arg(api_config, 2, "expert_routemap_topk"): + num_experts = self.get_arg(api_config, 4, "num_experts", 32) + if isinstance(num_experts, TensorConfig): + num_experts = 32 + seqlen, topk = self.shape[0], self.shape[1] + # Generate valid routemap vectorized for large seqlen + routemap = numpy.full(self.shape, -1, dtype="int32") + # Each row randomly assigns 1~topk experts to random positions + n_assigned = numpy.random.randint(1, topk + 1, size=seqlen) + # For each possible n_assigned value, batch process all rows with that count + for n in range(1, topk + 1): + mask = n_assigned == n + count = int(mask.sum()) + if count == 0: + continue + # Generate random expert indices for these rows + expert_indices = numpy.array( + [ + numpy.random.choice(num_experts, size=n, replace=False) + for _ in range(count) + ], + dtype="int32", + ) + # Generate random positions for these rows + position_indices = numpy.array( + [ + numpy.random.choice(topk, size=n, replace=False) + for _ in range(count) + ], + dtype="int32", + ) + row_indices = numpy.where(mask)[0] + for j in range(n): + routemap[row_indices, position_indices[:, j]] = expert_indices[:, j] + self.numpy_tensor = routemap + # Update tokens_per_expert to match the generated routemap + tokens_count = [int(numpy.sum(routemap == e)) for e in range(num_experts)] + tokens_per_expert = self.get_arg(api_config, 5, "tokens_per_expert") + tokens_per_expert[:] = tokens_count + # expert_prob_topk (arg3): float32, shape [seqlen, topk], value in [0, 1] + elif self.check_arg(api_config, 3, "expert_prob_topk"): + routemap_config = self.get_arg(api_config, 2, "expert_routemap_topk") + probs = numpy.zeros(self.shape, dtype="float32") + if ( + isinstance(routemap_config, TensorConfig) + and routemap_config.numpy_tensor is not None + ): + mask = routemap_config.numpy_tensor >= 0 + raw = numpy.random.random(self.shape).astype("float32") * mask + row_sums = raw.sum(axis=1, keepdims=True) + row_sums[row_sums == 0] = 1.0 + probs = raw / row_sums + else: + probs = numpy.random.random(self.shape).astype("float32") + row_sums = probs.sum(axis=1, keepdims=True) + row_sums[row_sums == 0] = 1.0 + probs = probs / row_sums + self.numpy_tensor = probs + # tokens_per_expert (arg5): list[int], length = num_experts + # This is a list not a TensorConfig, handled by APIConfig parser + + elif api_config.api_name == "paddle.nn.functional.moe_unpermute": + # moe_unpermute(hidden_states_unzipped, zipped_expertwise_rowmap, + # expert_routemap_topk, token_prob_unzipped, + # total_zipped_tokens, num_experts, ...) + # expert_routemap_topk (arg2): int32, shape [seqlen, topk], value in [-1, num_experts) + if self.check_arg(api_config, 2, "expert_routemap_topk"): + num_experts = self.get_arg(api_config, 5, "num_experts", 32) + if isinstance(num_experts, TensorConfig): + num_experts = 32 + seqlen, topk = self.shape[0], self.shape[1] + # Generate valid routemap vectorized for large seqlen + routemap = numpy.full(self.shape, -1, dtype="int32") + n_assigned = numpy.random.randint(1, topk + 1, size=seqlen) + for n in range(1, topk + 1): + mask = n_assigned == n + count = int(mask.sum()) + if count == 0: + continue + expert_indices = numpy.array( + [ + numpy.random.choice(num_experts, size=n, replace=False) + for _ in range(count) + ], + dtype="int32", + ) + position_indices = numpy.array( + [ + numpy.random.choice(topk, size=n, replace=False) + for _ in range(count) + ], + dtype="int32", + ) + row_indices = numpy.where(mask)[0] + for j in range(n): + routemap[row_indices, position_indices[:, j]] = expert_indices[:, j] + self.numpy_tensor = routemap + # zipped_expertwise_rowmap (arg1): int32, shape [seqlen, num_experts] + # Needs to be valid rowmap based on routemap + elif self.check_arg(api_config, 1, "zipped_expertwise_rowmap"): + routemap_config = self.get_arg(api_config, 2, "expert_routemap_topk") + num_experts = self.get_arg(api_config, 5, "num_experts", 32) + if isinstance(num_experts, TensorConfig): + num_experts = 32 + seqlen = self.shape[0] + rowmap = numpy.full(self.shape, -1, dtype="int32") + if isinstance(routemap_config, TensorConfig): + if routemap_config.numpy_tensor is None: + routemap_config.get_numpy_tensor(api_config, index=2) + if routemap_config.numpy_tensor is not None: + # Build rowmap: for each expert, assign row indices in order + expert_counters = numpy.zeros(num_experts, dtype="int32") + for i in range(seqlen): + for e in range(num_experts): + if numpy.any(routemap_config.numpy_tensor[i] == e): + rowmap[i, e] = expert_counters[e] + expert_counters[e] += 1 + self.numpy_tensor = rowmap + # token_prob_unzipped (arg3): float32, value in [0, 1] + elif self.check_arg(api_config, 3, "token_prob_unzipped"): + self.numpy_tensor = numpy.random.random(self.shape).astype("float32") + + if self.numpy_tensor is None: + if self.shape == []: + if "int" in self.dtype: + scalar_val = numpy.random.randint(-65535, 65535) + self.numpy_tensor = numpy.array(scalar_val, dtype=self.dtype) + else: + if self.dtype.startswith("complex"): + real_val = (numpy.random.random() - 0.5) * 1.2 + imag_val = (numpy.random.random() - 0.5) * 1.2 + self.numpy_tensor = numpy.array( + real_val + 1j * imag_val, dtype=self.dtype + ) + else: + scalar_val = (numpy.random.random() - 0.5) * 1.2 + self.numpy_tensor = numpy.array(scalar_val, dtype=self.dtype) + elif self._use_gpu_cache(original_dtype): + self._get_gpu_cache_entry(api_config, original_dtype) + elif USE_CACHED_NUMPY and self.dtype not in ["int64", "float64"]: + self.numpy_tensor = self.get_cached_numpy(self.dtype, self.shape, scale=1.2) + else: + if "int" in self.dtype: + self.numpy_tensor = ( + numpy.random.randint(-65535, 65535, size=self.shape) + ).astype(self.dtype) + else: + if self.dtype.startswith("complex"): + real_dtype = "float32" if self.dtype == "complex64" else "float64" + real_part = ((numpy.random.random(self.shape) - 0.5) * 1.2).astype( + real_dtype + ) + imag_part = ((numpy.random.random(self.shape) - 0.5) * 1.2).astype( + real_dtype + ) + self.numpy_tensor = (real_part + 1j * imag_part).astype(self.dtype) + else: + self.numpy_tensor = ( + (numpy.random.random(self.shape) - 0.5) * 1.2 + ).astype(self.dtype) + + self.dtype = original_dtype + return self.numpy_tensor + + def get_paddle_tensor(self, api_config): + if self.paddle_tensor is None: + if self.numpy_tensor is None and self._use_gpu_cache(): + return self.get_gpu_paddle_tensor(api_config) + if not self.is_contiguous and self.strides is not None: + self.paddle_tensor = self._create_strided_paddle_tensor(api_config) + print( + f"[non-contiguous] target strides: {self.strides}, " + f"actual strides: {self.paddle_tensor.strides}, " + f"shape: {list(self.paddle_tensor.shape)}, " + f"dtype: {self.paddle_tensor.dtype}, " + f"is_contiguous: {self.paddle_tensor.is_contiguous()}" + ) + else: + intermediate_dtype = ( + "float32" + if self.dtype == "bfloat16" + else ( + "float16" if self.dtype in ["float8_e5m2", "float8_e4m3fn"] else self.dtype + ) + ) + self.paddle_tensor = paddle.to_tensor( + self.get_numpy_tensor(api_config), + dtype=intermediate_dtype, + place=self.place, + ) + + self.paddle_tensor.stop_gradient = False + if self.dtype == "bfloat16": + self.paddle_tensor = paddle.cast(self.paddle_tensor, dtype="bfloat16") + elif self.dtype in ["float8_e5m2", "float8_e4m3fn"]: + self.paddle_tensor = paddle.cast(self.paddle_tensor, dtype=self.dtype) + if TEST_NON_CONTIGUOUS: + if not self.shuffle_dims: + ndim = self.paddle_tensor.dim() + self.shuffle_dims = list(range(ndim)) + random.shuffle(self.shuffle_dims) + print("paddle shuffle:", self.shuffle_dims) + return paddle.transpose(self.paddle_tensor, self.shuffle_dims) + return self.paddle_tensor + + def _strided_storage_size(self): + storage_size = 1 + for i in range(len(self.shape)): + if self.shape[i] > 0: + storage_size += (self.shape[i] - 1) * self.strides[i] + return storage_size + + def _create_strided_paddle_tensor(self, api_config): + """Create a non-contiguous paddle tensor from the shared logical numpy input.""" + flag_name = "FLAGS_check_nan_inf" + original_flag = paddle.get_flags([flag_name]) + paddle.set_flags({flag_name: False}) + try: + intermediate_dtype = ( + "float16" if self.dtype in ["float8_e5m2", "float8_e4m3fn"] else self.dtype + ) + storage_size = self._strided_storage_size() + flat_tensor = paddle.zeros( + [storage_size], + dtype=intermediate_dtype, + device=self.place, + ) + tensor = paddle.as_strided(flat_tensor, self.shape, self.strides) + logical_tensor = self.get_numpy_tensor(api_config) + if logical_tensor.size > 0: + tensor[...] = paddle.to_tensor( + logical_tensor, + dtype=intermediate_dtype, + place=self.place, + ) + if self.dtype in ["float8_e5m2", "float8_e4m3fn"]: + flat_tensor = paddle.cast(flat_tensor, dtype=self.dtype) + tensor = paddle.as_strided(flat_tensor, self.shape, self.strides) + + tensor.stop_gradient = False + return tensor + finally: + paddle.set_flags(original_flag) + + def get_torch_tensor(self, api_config): + device = torch.device("cuda:0") if torch.cuda.is_available() else torch.device("cpu") + torch.set_default_device(device) + if self.torch_tensor is None: + if self.numpy_tensor is None and self._use_gpu_cache(): + return self.get_gpu_torch_tensor(api_config) + if not self.is_contiguous and self.strides is not None: + self.torch_tensor = self._create_strided_torch_tensor(api_config) + else: + needs_intermediate = self.dtype in ["bfloat16", "float8_e5m2", "float8_e4m3fn"] + if needs_intermediate: + intermediate_torch_dtype = ( + torch.float32 if self.dtype == "bfloat16" else torch.float16 + ) + else: + intermediate_torch_dtype = self.convert_dtype_to_torch_type(self.dtype) + self.torch_tensor = torch.tensor( + self.get_numpy_tensor(api_config), + dtype=intermediate_torch_dtype, + requires_grad=self.dtype + in [ + "float32", + "float64", + "float16", + "complex64", + "complex128", + "bfloat16", + ], + ) + if self.dtype == "bfloat16": + self.torch_tensor = self.torch_tensor.to(dtype=torch.bfloat16) + elif self.dtype in ["float8_e5m2", "float8_e4m3fn"]: + self.torch_tensor = self.torch_tensor.to( + dtype=self.convert_dtype_to_torch_type(self.dtype) + ) + if TEST_NON_CONTIGUOUS: + if not self.shuffle_dims: + ndim = self.torch_tensor.dim() + self.shuffle_dims = list(range(ndim)) + random.shuffle(self.shuffle_dims) + print("torch shuffle:", self.shuffle_dims) + return torch.permute(self.torch_tensor, self.shuffle_dims) + return self.torch_tensor + + def _create_strided_torch_tensor(self, api_config): + """Create a non-contiguous torch tensor from the shared logical numpy input.""" + device = torch.device("cuda:0") if torch.cuda.is_available() else torch.device("cpu") + needs_intermediate = self.dtype in ["float8_e5m2", "float8_e4m3fn"] + if needs_intermediate: + intermediate_torch_dtype = torch.float16 + else: + intermediate_torch_dtype = self.convert_dtype_to_torch_type(self.dtype) + + flat_tensor = torch.empty( + self._strided_storage_size(), + dtype=intermediate_torch_dtype, + device=device, + ) + tensor = torch.as_strided(flat_tensor, self.shape, self.strides) + logical_tensor = self.get_numpy_tensor(api_config) + if logical_tensor.size > 0: + tensor.copy_( + torch.tensor( + logical_tensor, + dtype=intermediate_torch_dtype, + device=device, + ) + ) + if self.dtype in ["float8_e5m2", "float8_e4m3fn"]: + flat_tensor = flat_tensor.to(dtype=self.convert_dtype_to_torch_type(self.dtype)) + tensor = torch.as_strided(flat_tensor, self.shape, self.strides) + + requires_grad = self.dtype in [ + "float32", + "float64", + "float16", + "complex64", + "complex128", + "bfloat16", + ] + if requires_grad: + tensor = tensor.detach().requires_grad_(True) + return tensor + + def clear_tensor(self): + self.torch_tensor = None + self.paddle_tensor = None + self.numpy_tensor = None + if not should_skip_gpu_cleanup(): + torch.cuda.empty_cache() + paddle.device.cuda.empty_cache() + + def clear_paddle_tensor(self): + del self.paddle_tensor + self.paddle_tensor = None + if not should_skip_gpu_cleanup(): + paddle.device.cuda.empty_cache() + + def clear_numpy_tensor(self): + del self.numpy_tensor + self.numpy_tensor = None + + def clear_torch_tensor(self): + del self.torch_tensor + self.torch_tensor = None + if not should_skip_gpu_cleanup(): + torch.cuda.empty_cache() + + def fill_numpy_tensor(self, full_value): + self.numpy_tensor = numpy.full(shape=self.shape, fill_value=full_value, dtype=self.dtype) + + def check_arg(self, api_config, arg_pos, arg_name): + """检查api_config中的参数是否与当前实例匹配。 + 必须同时提供参数位置与参数名称, 具体请查看API文档。 + + Args: + api_config (ApiConfig): API配置对象, 包含args和kwargs。 + arg_pos (int): 参数的位置索引。 + arg_name (str): 参数的名称。 + + Returns: + bool: 如果参数匹配当前实例,则返回 True; 否则返回 False。 + + """ + return (hasattr(self, "index") and self.index == arg_pos) or ( + hasattr(self, "key") and self.key == arg_name + ) + + def get_arg(self, api_config, arg_pos, arg_name, default=None): + """从api_config中获取参数值。 + 必须同时提供参数位置与参数名称, 具体请查看API文档。 + + Args: + api_config (ApiConfig): API配置对象, 包含args和kwargs。 + arg_pos (int): 参数的位置索引。 + arg_name (str): 参数的名称。 + default (Any, optional): 参数的默认值。默认为None。 + + Returns: + Any: 参数的值。如果参数位置索引有效, 则返回args列表中对应位置的值; + 如果参数名称在kwargs字典中存在, 则返回对应名称的值; + 否则返回默认值。 + + """ + if 0 <= arg_pos < len(api_config.args): + return api_config.args[arg_pos] + if arg_name in api_config.kwargs: + return api_config.kwargs[arg_name] + return default + + def get_initialized_value(self, api_config, arg_pos=None, arg_name=None): + """Get the initialized numpy_tensor value from the api_config instead of the TensorConfig""" + # for uninitialized numpy_tensor, return None implicitly as numpy_tensor is None + if arg_pos is not None and 0 <= arg_pos < len(api_config.args): + if isinstance(api_config.args[arg_pos], TensorConfig): + return api_config.args[arg_pos].numpy_tensor + else: + return api_config.args[arg_pos] + if arg_name and arg_name in api_config.kwargs: + if isinstance(api_config.kwargs[arg_name], TensorConfig): + return api_config.kwargs[arg_name].numpy_tensor + else: + return api_config.kwargs[arg_name] + # for args that does not appear in api_config + if arg_pos >= len(api_config.args) or arg_name not in api_config.kwargs: + return None + # error case + if arg_pos is None and arg_name is None: + raise ValueError("either arg_pos or arg_name must be provided.") + elif arg_pos: + if arg_pos < 0: + raise IndexError( + f"argument position {arg_pos} is out of range for api_config with {len(api_config.args)} arguments." + ) + else: + # case type(api_config.args[arg_pos]) != TensorConfig: + raise TypeError(f"argument at position {arg_pos} is not of type TensorConfig.") + else: + # case type(api_config.kwargs[arg_name]) != TensorConfig: + raise TypeError(f"argument '{arg_name}' is not of type TensorConfig.") + + def set_tensor_arg_value(self, api_config, arg_pos=None, arg_name=None, value=None): + if ( + arg_pos is not None + and 0 <= arg_pos < len(api_config.args) + and isinstance(api_config.args[arg_pos], TensorConfig) + ): + api_config.args[arg_pos].numpy_tensor = value + elif ( + arg_name + and arg_name in api_config.kwargs + and isinstance(api_config.kwargs[arg_name], TensorConfig) + ): + api_config.kwargs[arg_name].numpy_tensor = value + else: + raise ValueError( + f"argument at position {arg_pos} or name '{arg_name}' is not of type TensorConfig." + ) + + def get_random_numpy_tensor(self, shape=None, data_type=None, min=None, max=None): + """Generate a random numpy tensor with data in [min, max) given shape and data_type""" + if "int" in data_type: + min = min if min is not None else -65535 + max = max if max is not None else 65535 + numpy_tensor = (numpy.random.randint(min, max, size=shape)).astype(data_type) + elif data_type.startswith("complex"): + real_dtype = "float32" if data_type == "complex64" else "float64" + real_min = min if min is not None else numpy.finfo(real_dtype).min / 2 + real_max = max if max is not None else numpy.finfo(real_dtype).max / 2 + real_part = numpy.random.uniform(real_min, real_max, size=shape).astype(real_dtype) + imag_part = numpy.random.uniform(real_min, real_max, size=shape).astype(real_dtype) + numpy_tensor = (real_part + 1j * imag_part).astype(data_type) + else: + dtype = "float32" if data_type == "bfloat16" else data_type + min = min if min is not None else numpy.finfo(dtype).min / 2 + max = max if max is not None else numpy.finfo(dtype).max / 2 + numpy_tensor = (numpy.random.uniform(min, max, size=shape)).astype(dtype) + return numpy_tensor + + +class APIConfig: + def __deepcopy__(self, memo): + cls = self.__class__ + result = cls.__new__(cls) + memo[id(self)] = result + result.args = copy.deepcopy(self.args) + result.kwargs = copy.deepcopy(self.kwargs) + result.api_name = self.api_name + return result + + def __init__(self, config): + config = config.replace("\n", "") + self.config = config + self.args = [] + self.kwargs = collections.OrderedDict() + + # 兼容 paddle.Size([...]) 格式:将其替换为 [...] + def replace_paddle_size(match): + shape_list = match.group(1) # 提取 [...] 部分 + return shape_list + + config = re.sub(r"paddle\.Size\(\s*(\[[^\]]*\])\s*\)", replace_paddle_size, config) + config = config.replace("Tensor(", "TensorConfig(") + + self.api_name, offset = self.get_api(config) + + if self.api_name == "paddle.einsum": + tmp = config[config.index('"') + 1 :] + value = tmp[: tmp.index('"')] + offset = config.index('"') + 1 + tmp.index('"') + if "equation" in config: + self.append_kwargs("equation", value) + else: + self.append_args(value) + + while True: + prev_offset = offset + token, offset = self.get_token(config, offset) + if offset is None: + # Check for empty string "" that get_token cannot match + remaining = config[prev_offset:] + idx = remaining.find('""') + if idx >= 0: + offset = prev_offset + idx + 2 + self.append_args("") + continue + return + + is_kwarg = config[offset] == "=" + if is_kwarg: + key = token + prev_offset2 = offset + 1 + token, offset = self.get_token(config, prev_offset2) + # Handle kwarg with empty string value: key="" + if token is None: + remaining = config[prev_offset2:] + idx = remaining.find('""') + if idx >= 0: + offset = prev_offset2 + idx + 2 + self.append_kwargs(key, "") + continue + else: + return + + value, offset = self.get_one_arg(token, config, offset) + + if offset is None: + return + + if is_kwarg: + self.append_kwargs(key, value) + else: + self.append_args(value) + + def append_args(self, arg): + self.args.append(arg) + + def append_kwargs(self, name, arg): + self.kwargs[name] = arg + + def dump_item_str(self, item): + type_mapping = { + numpy.int16: int, + numpy.int32: int, + numpy.int64: int, + numpy.float16: float, + numpy.float32: float, + numpy.float64: float, + numpy.integer: int, + numpy.floating: float, + numpy.bool_: bool, + numpy.complexfloating: complex, + numpy.str_: str, + numpy.bytes_: bytes, + # numpy.unicode_: str, + } + for numpy_type, builtin_type in type_mapping.items(): + if isinstance(item, numpy_type): + item = builtin_type(item) + break + + if isinstance(item, TensorConfig): + return str(item) + elif isinstance(item, paddle.base.core.DataType): + return "Dtype(" + str(item)[7:] + ")" + elif isinstance(item, paddle.base.core.VarDesc.VarType): + return "VarType(" + str(item)[7:] + ")" + elif isinstance(item, list): + result = "list[" + for sub_item in item: + tmp = self.dump_item_str(sub_item) + if tmp == "": + return "" + result = result + tmp + "," + result = result + "]" + return result + elif isinstance(item, tuple): + result = "tuple(" + for sub_item in item: + tmp = self.dump_item_str(sub_item) + if tmp == "": + return "" + result = result + tmp + "," + result = result + ")" + return result + elif isinstance(item, slice): + return "slice(" + str(item.start) + "," + str(item.stop) + "," + str(item.step) + ")" + elif isinstance(item, complex): + return ( + "complex(" + + self.dump_item_str(item.real) + + "," + + self.dump_item_str(item.imag) + + ")" + ) + elif item is None: + return "None" + elif isinstance(item, (paddle.base.Variable, paddle.base.libpaddle.pir.Value)): + return "" + elif item == math.inf: + return "math.inf" + elif item == -math.inf: + return "-math.inf" + elif item == math.nan: + return "math.nan" + elif item == -math.nan: + return "-math.nan" + elif isinstance(item, (bool, int, float)): + return str(item) + elif isinstance(item, str): + return '"' + item + '"' + elif isinstance(item, type): + return "type(" + str(item)[str(item).index("'") + 1 : str(item).rindex("'")] + ")" + elif callable(item): + name = getattr(item, '__name__', None) or getattr(item, '__qualname__', None) + if name: + return "callable(" + name + ")" + return "callable(unknown)" + else: + return str(item) + + def __str__(self): + result = self.api_name + "(" + for arg in self.args: + result += self.dump_item_str(arg) + ", " + for key, value in self.kwargs.items(): + result += key + "=" + self.dump_item_str(value) + ", " + result += ")" + return result + + def __repr__(self): + return self.__str__() + + # def get_token(self, config, offset): + # def is_int(token): + # try: + # int(token) + # return True + # except Exception as err: + # return False + # pattern = r'\b[A-Za-z0-9._+-]+\b|-[A-Za-z0-9._+-]+\b' + # match = re.search(pattern, config[offset:]) + # if match: + # if is_int(match.group()) and config[offset + match.start() + len(match.group())] == ".": + # return match.group()+".", offset + match.start() + len(match.group()) + 1 + # return match.group(), offset + match.start() + len(match.group()) + # return None, None + + def get_token(self, config, offset): + def is_int(token): + try: + int(token) + return True + except Exception: + return False + + # Modified pattern to handle decimal numbers starting with dot + pattern = r"\b[A-Za-z0-9._+-]+\b|-[A-Za-z0-9._+-]+\b|\.[0-9]+" + match = re.search(pattern, config[offset:]) + if match: + token = match.group() + # Handle the case where token starts with dot followed by digits + if token.startswith(".") and token[1:].isdigit(): + return token, offset + match.start() + len(token) + + if ( + is_int(token) + and offset + match.start() + len(token) < len(config) + and config[offset + match.start() + len(token)] == "." + ): + return token + ".", offset + match.start() + len(token) + 1 + return token, offset + match.start() + len(token) + return None, None + + def get_api(self, config): + return config[0 : config.index("(")], len(config[0 : config.index("(")]) + + def get_tensor(self, config, offset): + config = config[offset:] + tensor_str = config[config.index("TensorConfig") : config.index(")") + 1] + return eval(tensor_str), offset + len(tensor_str) + + def get_dtype(self, config, offset): + token, offset = self.get_token(config, offset) + if hasattr(paddle.framework, "convert_nptype_to_datatype_or_vartype"): + return paddle.framework.convert_nptype_to_datatype_or_vartype(token), offset + # fallback for older Paddle versions + return paddle.pir.core.convert_np_dtype_to_dtype_(token), offset + + def get_place(self, config, offset): + """Parse Place(gpu:0), Place(cpu), etc.""" + config_slice = config[offset:] + place_str = config_slice[config_slice.index("(") + 1 : config_slice.index(")")] + end_offset = offset + config_slice.index(")") + 1 + if place_str == "cpu": + return paddle.CPUPlace(), end_offset + elif place_str.startswith("gpu"): + if ":" in place_str: + device_id = int(place_str.split(":")[1]) + else: + device_id = 0 + gpu_count = paddle.device.cuda.device_count() + if gpu_count > 0: + device_id = device_id % gpu_count + return paddle.CUDAPlace(device_id), end_offset + else: + return paddle.CPUPlace(), end_offset + + def get_vartype(self, config, offset): + token, offset = self.get_token(config, offset) + return paddle.base.framework.convert_np_dtype_to_proto_type(token), offset + + def get_list(self, config, offset): + result = [] + tmp = 0 + last_index = offset + for i in range(offset, len(config)): + if config[i] == "[": + tmp = tmp + 1 + if config[i] == "]": + tmp = tmp - 1 + if tmp == 0: + last_index = i + break + + list_str = config[offset : last_index + 1] + if "TensorConfig" not in list_str: + list_str = list_str.replace(",", " ") + + offset = 1 + while True: + token, offset = self.get_token(list_str, offset) + if offset is None: + break + + value, offset = self.get_one_arg(token, list_str, offset) + + if offset is None: + break + + result.append(value) + + return result, last_index + 1 + + def get_tuple(self, config, offset): + result = [] + tmp = 0 + last_index = offset + for i in range(offset, len(config)): + if config[i] == "(": + tmp = tmp + 1 + if config[i] == ")": + tmp = tmp - 1 + if tmp == 0: + last_index = i + break + + tuple_str = config[offset : last_index + 1] + + tuple_str = tuple_str.replace(",", " , ") + + offset = 1 + while True: + token, offset = self.get_token(tuple_str, offset) + if offset is None: + break + + value, offset = self.get_one_arg(token, tuple_str, offset) + + if offset is None: + break + + result.append(value) + + return tuple(result), last_index + 1 + + def get_slice(self, config, offset): + config = config[offset:] + slice_str = config[config.index("(") : config.index(")") + 1] + return eval("slice" + slice_str), offset + len(slice_str) + + def get_complex(self, config, offset): + config = config[offset:] + complex_str = config[config.index("(") : config.index(")") + 1] + if "nan" in complex_str and complex_str[complex_str.index("nan") - 1] != ".": + complex_str = complex_str.replace("nan", "float('nan')") + return eval("complex" + complex_str), offset + len(complex_str) + + def get_numpy_type(self, config, offset): + config = config[offset:] + numpy_type_str = config[config.index("(") + 1 : config.index(")")] + if numpy_type_str == "numpy.bool": + return numpy.bool_, offset + len(numpy_type_str) + 2 + return eval(numpy_type_str), offset + len(numpy_type_str) + 2 + + def get_one_arg(self, token, config, offset): + if token == "TensorConfig": + value, offset = self.get_tensor(config, offset - len(token)) + elif token == "Dtype": + value, offset = self.get_dtype(config, offset) + elif token == "Place": + value, offset = self.get_place(config, offset) + elif token == "VarType": + value, offset = self.get_vartype(config, offset) + elif token == "list": + value, offset = self.get_list(config, offset) + elif token == "tuple": + value, offset = self.get_tuple(config, offset) + elif token == "slice": + value, offset = self.get_slice(config, offset) + elif token == "complex": + value, offset = self.get_complex(config, offset) + elif token == "type": + value, offset = self.get_numpy_type(config, offset) + elif token == "callable": + # parse callable(name) format - store as a string marker + start = config.index("(", offset - len(token)) + 1 + end = config.index(")", start) + value = config[start:end] # store the callable name as string + offset = end + 1 + elif token == "nan": + value = float("nan") + elif token is not None and config[offset - len(token) - 1] == '"': + # fix token is not correct in str with spaces + next_quote_idx = config.index('"', offset) + value = config[offset - len(token) : next_quote_idx] + offset = next_quote_idx + elif token is None: + return None, None + else: + if token[0] == ".": + token = "0" + token + value = eval(token) + return value, offset + + +# def analyse_configs(config_path): +# with open(config_path) as f: +# configs = f.readlines() +# f.close() + +# api_configs = [] +# for config in configs: +# # print(config) +# api_config = APIConfig(config) +# api_configs.append(api_config) +# return api_configs + +def analyse_configs(config_path): + with open(config_path) as f: + configs = f.readlines() + + api_configs = [] + for config in configs: + config = config.strip() + if not config or "(" not in config: + continue + api_config = APIConfig(config) + api_configs.append(api_config) + return api_configs + \ No newline at end of file diff --git a/tools/qa_test/dedup_config.py b/tools/qa_test/dedup_config.py new file mode 100644 index 00000000..a207c1a2 --- /dev/null +++ b/tools/qa_test/dedup_config.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""Deduplicate configuration lines while preserving sorted unique output. + +Usage: + python dedup_config.py -i api_config_0_size.txt + python dedup_config.py -i api_config_0_size.txt -o /output/dir/dedup.txt +""" +import argparse +import os + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Deduplicate configuration lines while preserving sorted unique output.", + ) + parser.add_argument("-i", "--input", required=True, + help="Input config file path") + parser.add_argument("-o", "--output", default=None, + help="Output file path. Default: _dedup.txt in same dir as input") + return parser.parse_args() + + +def main(): + args = parse_args() + + input_path = args.input + if args.output: + output_path = args.output + else: + stem, ext = os.path.splitext(input_path) + output_path = f"{stem}_dedup{ext}" + + output_dir = os.path.dirname(output_path) + if output_dir: + os.makedirs(output_dir, exist_ok=True) + + seen = set() + total = 0 + with open(input_path, "r", encoding="utf-8") as f: + for line in f: + total += 1 + seen.add(line.rstrip("\n")) + + unique_lines = sorted(seen) + + with open(output_path, "w", encoding="utf-8") as f: + for line in unique_lines: + f.write(line + "\n") + + print(f"Total lines: {total}") + print(f"Unique lines: {len(unique_lines)}") + print(f"Duplicates: {total - len(unique_lines)}") + print(f"Output: {output_path}") + + +if __name__ == "__main__": + main() diff --git a/tools/qa_test/derive_api_seq.py b/tools/qa_test/derive_api_seq.py new file mode 100644 index 00000000..cc1e23d1 --- /dev/null +++ b/tools/qa_test/derive_api_seq.py @@ -0,0 +1,638 @@ +#!/usr/bin/env python3 +""" +API config 推导脚本:由两个不同 seq 的配置推导任意目标 seq(含 MoE 结构重建)。 + +文件名:derive_api_seq.py +用法: + python derive_api_seq.py 4096 --small api_config_1024.txt --large api_config_2048.txt + python derive_api_seq.py 1048576 --small cfg_1024.txt --large cfg_2048.txt -o api_config_1M.txt + python derive_api_seq.py 4096 # 使用默认(脚本目录下 api_config_1024/2048.txt) + +────────────────────────────── 两类行的处理 ────────────────────────────── +配置里的行分两类: + +A. 确定类(结构/维度随 seq 仿射变化,可精确推导) + 逐项比对 small/large 得到:任一会变的整数 v 关于 seq 仿射 v = α·seq + β。 + 锚定在 small:mult = (TARGET-small)/small,v(T) = v_small + (v_large-v_small)·mult。 + 按 (结构签名, 数字位置) 学习 {v_large -> v_target} 映射改写。 + 常量(hidden=7168、专家数、词表等模型维度)多重集两边相等,保持不变。 + +B. MoE dispatch 类(router 数据相关,逐 token 路由,不可照抄) + 这族行的数值(每专家 token 数、padded buffer 维度、dispatch 切片边界) + 是数据相关的,small 与 large 原始重叠极低,旧脚本「照抄 large 骨架」不正确。 + 本脚本改为「约束重采样」结构重建: + 1. 共 144 个 moe_permute 调用(跨 seq 恒定)。总 token 预算 = 144·seq·4, + 按 per_call_profile(144 维,真实统计的各调用占比)分配给每个调用, + 得到该调用的 Σtpe(每调用 token 总量)。 + 2. 单个调用内:20 个专家,按 expert_profile(20 维升序负载占比)把 Σtpe + 拆成 20 个 tokens_per_expert(保证 Σ=该调用预算、整数、非负)。 + 3. padded buffer N = Σ ceil(tpe_e/128)·128(128 对齐,与真实一致)。 + input_N = round(Σtpe / 1.2033)(真实统计的固定比值)。 + 4. 用固定随机种子施加轻微抖动后再归一化,保证可复现且不是机械等比。 + 5. 把重建出的 tpe / N / input_N / dispatch 切片边界回填到该 MoE block 的 + moe_permute、下游 reshape/transpose/getitem 切片、moe_unpermute 各行。 +""" + +import argparse +import os +import re +import sys +import json +import math +import random +from collections import defaultdict, Counter + +# ──────────────────────────── 配置(运行时由 parse_args 填充) ──────────────────────────── + +SEQ_SMALL = None +SEQ_LARGE = None +SEQ_TARGET = None +MULT = None +RANDOM_SEED = None +JITTER = None + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +# 真实 padded buffer 取值集合(运行时由骨架填充)。第 2 类缓冲行缩放只动 N 属于此集合者。 +MOE_PADDED_N = set() +MOE_PADDED_N_RAW = set() + +# ──────────────────────────── 内置负载模板 ──────────────────────────── +# 原 moe_profile.json 的内容,由真实 4096/8192 抓取统计而来,直接内联。 +# expert_profile_20: 单个 moe_permute 调用内 20 专家的升序负载占比(归一化)。 +# per_call_profile_144: 144 个调用各自占总 Σtpe 的比例(升序,归一化)。 +# 总 Σtpe = 144 * seq * 4(每调用均值 = seq*4)。input_N = round(Σtpe/1.2033)。 +# padded buffer N = Σ ceil(tpe_e/128)*128。 +MOE_PROFILE = { + "expert_profile_20": [0.004221, 0.009402, 0.013823, 0.017920, 0.021618, 0.024818, 0.027641, 0.031208, 0.034247, 0.037616, 0.041644, 0.045602, 0.050711, 0.055962, 0.062604, 0.070290, 0.078967, 0.091881, 0.117033, 0.162790], + "per_call_profile_144": [0.003664, 0.003801, 0.004234, 0.004444, 0.004525, 0.004777, 0.004919, 0.005035, 0.005125, 0.005139, 0.005180, 0.005205, 0.005312, 0.005355, 0.005375, 0.005457, 0.005564, 0.005601, 0.005675, 0.005703, 0.005709, 0.005789, 0.005803, 0.005829, 0.005858, 0.005963, 0.006043, 0.006070, 0.006091, 0.006100, 0.006104, 0.006126, 0.006133, 0.006140, 0.006171, 0.006200, 0.006234, 0.006255, 0.006274, 0.006280, 0.006307, 0.006346, 0.006366, 0.006374, 0.006409, 0.006424, 0.006438, 0.006467, 0.006474, 0.006490, 0.006528, 0.006545, 0.006573, 0.006583, 0.006598, 0.006636, 0.006648, 0.006671, 0.006675, 0.006681, 0.006692, 0.006706, 0.006721, 0.006727, 0.006731, 0.006735, 0.006742, 0.006753, 0.006800, 0.006809, 0.006825, 0.006849, 0.006865, 0.006934, 0.006941, 0.006973, 0.006980, 0.006985, 0.007008, 0.007014, 0.007018, 0.007032, 0.007048, 0.007081, 0.007096, 0.007136, 0.007147, 0.007174, 0.007184, 0.007203, 0.007225, 0.007232, 0.007273, 0.007285, 0.007297, 0.007313, 0.007364, 0.007392, 0.007431, 0.007452, 0.007475, 0.007515, 0.007547, 0.007573, 0.007606, 0.007624, 0.007629, 0.007691, 0.007701, 0.007715, 0.007758, 0.007781, 0.007834, 0.007887, 0.007920, 0.007941, 0.007958, 0.008033, 0.008083, 0.008139, 0.008153, 0.008231, 0.008283, 0.008317, 0.008353, 0.008394, 0.008409, 0.008418, 0.008460, 0.008569, 0.008642, 0.008678, 0.008729, 0.008813, 0.008845, 0.008858, 0.008933, 0.009125, 0.009336, 0.009394, 0.009520, 0.009638, 0.009690, 0.010134], + "input_N_divisor": 1.2033, + "padding_alignment": 128, + "num_experts": 20, + "top_k": 4, + "hidden": 7168, + "moe_permute_calls": 144, +} + +# ──────────────────────────── 正则 ──────────────────────────── + +NUM_RE = re.compile(r'-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?') +DTYPE_RE = re.compile(r'"[^"]*"|Dtype\([^)]*\)') # 保护区:内部数字不提取 +INT_RE = re.compile(r'-?\d+') + +# MoE 解析 +MOE_PERMUTE_RE = re.compile( + r'moe_permute\(Tensor\(paddle\.Size\(\[(\d+), 7168\]\).*?' + r'tokens_per_expert=list\[([^\]]*)\], padding_alignment=128' +) +SLICE_RE = re.compile(r'slice\((\d+),(\d+),None\)') + +# 模型结构常量:永不作为 MoE block 标量映射的 key(即使某 block 的 +# old_padded_N / old_input_N 偶然等于其中之一)。否则会把 Size([N,7168]) 里的 +# hidden=7168 等常量误当作 buffer 维度放大。56=top_k*hidden/512、28、3584=hidden/2、 +# 20=num_experts、4=top_k、143360/1120 等也是固定结构维度。 +MODEL_CONSTANTS = frozenset({ + 7168, 3584, 1792, 56, 28, 14, 20, 4, 8, 16, 32, 64, 128, + 143360, 1120, 256, 512, 1024, +}) + + +def signature(line): + """结构签名:把所有数字替换为 #。""" + return NUM_RE.sub('#', line) + + +def numbers(line): + """提取行内数字(跳过 dtype 字符串区),返回 [(text, start, end), ...]。""" + zones = [(m.start(), m.end()) for m in DTYPE_RE.finditer(line)] + out = [] + for m in NUM_RE.finditer(line): + if not any(s <= m.start() < e for s, e in zones): + out.append((m.group(), m.start(), m.end())) + return out + + +def is_int(text): + return INT_RE.fullmatch(text) is not None + + +# ──────────────────────────── 学习仿射映射(确定类) ──────────────────────────── + +def collect_position_values(lines): + """收集每个 (signature, position) 上出现过的所有整数。""" + table = defaultdict(lambda: defaultdict(list)) + for line in lines: + sig = signature(line) + for pos, (text, _, _) in enumerate(numbers(line)): + if is_int(text): + table[sig][pos].append(int(text)) + return table + + +def build_value_map(lines_small, lines_large): + """构建 (signature, position) -> {v_large: v_target} 的仿射映射。""" + small = collect_position_values(lines_small) + large = collect_position_values(lines_large) + + vmap = defaultdict(dict) + for sig, positions in large.items(): + for pos, larges in positions.items(): + smalls = small.get(sig, {}).get(pos, []) + cnt_s, cnt_l = Counter(smalls), Counter(larges) + if cnt_s == cnt_l: + continue # 整列常量,跳过 + common = cnt_s & cnt_l + rem_s = sorted((cnt_s - common).elements()) + rem_l = sorted((cnt_l - common).elements()) + if len(rem_s) != len(rem_l) or not rem_l: + continue # 个数随 seq 变(多为 MoE),跳过 + candidates = defaultdict(set) + for v_small, v_large in zip(rem_s, rem_l): + v_target = v_small + (v_large - v_small) * MULT + candidates[v_large].add(v_target) + for v_large, targets in candidates.items(): + if len(targets) == 1: # 无歧义才采用 + vmap[(sig, pos)][v_large] = next(iter(targets)) + return vmap + + +def derive_line(line, vmap): + """用仿射映射改写一行(确定类,以 seq=2048 行为骨架)。""" + sig = signature(line) + nums = numbers(line) + replacements = [] + for pos, (text, start, end) in enumerate(nums): + if not is_int(text): + continue + value = int(text) + mapping = vmap.get((sig, pos)) + if mapping and value in mapping and mapping[value] != value: + replacements.append((start, end, str(mapping[value]))) + if not replacements: + return line + result = line + for start, end, new_text in reversed(replacements): + result = result[:start] + new_text + result[end:] + return result + + +# ──────────────────────────── MoE 结构重建 ──────────────────────────── +# 见文件头 B 部分说明。 + +def _proportional_int_split(total, weights, rng, jitter): + """把整数 total 按 weights(占比,和为1)拆成 len(weights) 个非负整数, + 施加 ±jitter 抖动后用最大余数法保证求和精确等于 total。""" + n = len(weights) + jittered = [] + for w in weights: + factor = 1.0 + rng.uniform(-jitter, jitter) + jittered.append(max(w * factor, 1e-9)) + s = sum(jittered) + raw = [total * w / s for w in jittered] + floors = [int(math.floor(x)) for x in raw] + remainder = total - sum(floors) + # 把剩余按小数部分从大到小分配 + order = sorted(range(n), key=lambda i: raw[i] - floors[i], reverse=True) + for k in range(remainder): + floors[order[k % n]] += 1 + return floors + + +def load_profile(): + profile_path = os.path.join(BASE_DIR, "moe_profile.json") + if os.path.exists(profile_path): + with open(profile_path) as f: + return json.load(f) + return MOE_PROFILE + + +def build_moe_plan(profile): + """为目标 seq 生成 144 个调用的重建计划。 + 返回 list[dict],每项含 tpe(20)、sum_tpe、padded_each、padded_N、input_N。""" + rng = random.Random(RANDOM_SEED) + per_call = profile["per_call_profile_144"] + expert = profile["expert_profile_20"] + divisor = profile["input_N_divisor"] + pad = profile["padding_alignment"] + n_calls = profile["moe_permute_calls"] + + grand_total = n_calls * SEQ_TARGET * 4 # Σ over all calls 的 token 预算 + # 各调用的 Σtpe(按真实占比 + 抖动,整数,和为 grand_total) + call_sums = _proportional_int_split(grand_total, per_call, rng, JITTER) + + plans = [] + for csum in call_sums: + # 单调用内 20 专家拆分(升序模板) + tpe = _proportional_int_split(csum, expert, rng, JITTER) + # 每专家 token 数至少 1(避免空专家) + tpe = [max(t, 1) for t in tpe] + # 修正使 Σ 仍等于 csum + drift = csum - sum(tpe) + if drift != 0: + tpe[tpe.index(max(tpe))] += drift + # 真实数据中各调用的「重专家」位置是随机的(升序模板会固定让最后一个专家 + # 最重,不符合真实)。这里对 20 个专家做随机置换,使峰值专家逐调用变化。 + rng.shuffle(tpe) + padded_each = [math.ceil(t / pad) * pad for t in tpe] + padded_N = sum(padded_each) + input_N = int(round(csum / divisor)) + plans.append({ + "tpe": tpe, + "sum_tpe": sum(tpe), + "padded_each": padded_each, + "padded_N": padded_N, + "input_N": input_N, + }) + return plans + + +def _moe_block_bounds(lines): + """切出每个 MoE block 的 [permute_idx, block_end)。 + block 从一个 moe_permute 行开始,到下一个 moe_permute / moe_unpermute 之后 + 的边界为止。这里采用:每个 permute 与其后第一个 unpermute 之间为一个 block, + unpermute 行本身归入该 block 末尾。返回 [(p_idx, u_idx), ...]。 + permute 有 fp8 / bf16 两种变体,各 144 个;每个 permute 都配一个 unpermute。 + """ + perm_idx = [i for i, l in enumerate(lines) if 'functional.moe_permute' in l] + unperm_idx = [i for i, l in enumerate(lines) if 'functional.moe_unpermute' in l] + blocks = [] + used = set() + for p in perm_idx: + # 该 permute 后第一个尚未占用的 unpermute + u = next((x for x in unperm_idx if x > p and x not in used), None) + if u is None: + u = len(lines) - 1 + else: + used.add(u) + blocks.append((p, u)) + return blocks + + +def _parse_permute(line): + """解析 moe_permute 行,返回 (variant, input_N, tpe_list) 或 None。""" + m = MOE_PERMUTE_RE.search(line) # fp8 变体(带 tokens_per_expert=) + if m: + tpe = [int(x) for x in m.group(2).split(',') if x.strip()] + return ('fp8', int(m.group(1)), tpe) + m = re.search( + r'moe_permute\(Tensor\(paddle\.Size\(\[(\d+), 7168\]\),"bfloat16"\), ' + r'None,.*?, 20, list\[([^\]]*)\], padding_alignment=128', line) + if m: + tpe = [int(x) for x in m.group(2).split(',') if x.strip()] + return ('bf16', int(m.group(1)), tpe) + return None + + +def _rewrite_int_run(line, old_to_new): + """把 line 中等于某个 old 的整数替换为对应 new(保护 dtype 区)。 + old_to_new 是 dict[int,int];只替换恰好匹配的整数 token。""" + nums = numbers(line) + repl = [] + for text, start, end in nums: + if is_int(text): + v = int(text) + if v in old_to_new and old_to_new[v] != v: + repl.append((start, end, str(old_to_new[v]))) + if not repl: + return line + for start, end, nt in reversed(repl): + line = line[:start] + nt + line[end:] + return line + + +def _rewrite_permute_line(line, variant, new_input_N, new_tpe): + """重写 moe_permute 行:替换 input_N 与 tokens_per_expert 列表。""" + new_list = ','.join(str(t) for t in new_tpe) + ',' + if variant == 'fp8': + line = MOE_PERMUTE_RE.sub( + lambda m: m.group(0).replace( + f'Size([{m.group(1)}, 7168]', f'Size([{new_input_N}, 7168]' + ).replace( + f'tokens_per_expert=list[{m.group(2)}]', + f'tokens_per_expert=list[{new_list}]' + ), + line, count=1) + # input_N 还出现在同一行其它 Tensor 的第一维([N,56] [N,4]) + m2 = _parse_permute(line) + else: + m = re.search( + r'moe_permute\(Tensor\(paddle\.Size\(\[(\d+), 7168\]\),"bfloat16"\), ' + r'None,.*?, 20, list\[([^\]]*)\], padding_alignment=128', line) + old_in, old_list = m.group(1), m.group(2) + line = line.replace(f'list[{old_list}]', f'list[{new_list}]', 1) + return line + + +def apply_moe_reconstruction(derived, skeleton, moe_plan, profile): + """用重建计划覆盖每个 MoE block 内的数据相关数值。 + + derived : 已做完确定类仿射的行(将被原地改写 MoE block)。 + skeleton : 未改写的 2048 原始行,用于识别 MoE 数据相关行的旧值。 + + ── 为什么不能用「按 block 内全局值替换」 ── + seq=2048 里 144 个 fp8 permute 全部排在 144 个 unpermute 之前,导致 _moe_block_bounds + 切出的 block 互相重叠(实测单行最多被 8 个 block 覆盖)。同一条 getitem 行会被多个 + block 各自的 scalar_map / bound_map 反复改写,结果出现 slice(186752,640) 这种 + start>end 的脏值,且无法判断该行究竟属于哪个 block。 + + ── 改为两类处理 ── + 1. permute 行:每个 permute 唯一携带自身重采样计划的 tpe / input_N,按 block 精确改写。 + 2. 其余 MoE 缓冲行(getitem / zero_ / assign / slice / empty / reshape / transpose 等 + 带 [N,7168|3584|56|28] padded-buffer 维的行):用全局线性比例 r=SEQ_TARGET/SEQ_LARGE + 做「逐行局部缩放」——buffer 维 N 和 slice 起止 (a,b) 都按 r 缩放并对齐到 128。 + 这与真实 padded buffer 随 seq 近似线性增长一致,且天然保证 128 对齐、slice<=N、 + start<=end,完全不受 block 重叠影响。统计上每行 N 等于「该调用 padded 均值的缩放」, + 与逐 block 精确值仅差每调用方差,对合成配置的形状/量级保真度完全够用。 + """ + pad = profile["padding_alignment"] + r = SEQ_TARGET / SEQ_LARGE # 全局缩放比(buffer 维近似线性) + + def scale128(x, mode="round"): + """把 x 按比例 r 缩放并对齐到 128。mode: round/floor/ceil。""" + y = x * r / pad + if mode == "floor": + k = math.floor(y) + elif mode == "ceil": + k = math.ceil(y) + else: + k = round(y) + return int(k) * pad + + # 真实 padded buffer 取值集合(来自 2048 骨架所有 permute block 的 Σceil(tpe/128)·128)。 + # 第 2 类缩放只动 N ∈ 该集合的行,从而避开 [3584,7168] / [102400,7168] 等常量首维。 + # 关键:剔除与模型结构常量重合的 padded 值(如 2048 时恰有 6 个 block 的 padded_N==7168, + # 但 7168 同时是 hidden 维 / 权重 reshape 行数 list[7168,7168,],无法区分语义)。 + # 对任何 target seq>=4096,真实 padded_N 都 >9000,永不等于 7168,故剔除 7168 零损失。 + global MOE_PADDED_N, MOE_PADDED_N_RAW + MOE_PADDED_N_RAW = set() + for l in skeleton: + if 'moe_permute' not in l: + continue + pp = _parse_permute(l) + if pp: + MOE_PADDED_N_RAW.add(sum(math.ceil(t / pad) * pad for t in pp[2])) + # 用于「可能是权重」的形状参数:剔除与结构常量重合的 padded 值(如 7168) + MOE_PADDED_N = MOE_PADDED_N_RAW - MODEL_CONSTANTS + + # ── 第 1 类:permute 行精确改写(按 block) ── + blocks = _moe_block_bounds(skeleton) + # bf16 变体独立再采样一份(与 fp8 路由不同,预算分布相同) + rng2 = random.Random(RANDOM_SEED + 1) + bf_plan = [] + per_call = profile["per_call_profile_144"] + expert = profile["expert_profile_20"] + divisor = profile["input_N_divisor"] + n_calls = profile["moe_permute_calls"] + grand_total = n_calls * SEQ_TARGET * 4 + bf_sums = _proportional_int_split(grand_total, per_call, rng2, JITTER) + for csum in bf_sums: + tpe = _proportional_int_split(csum, expert, rng2, JITTER) + tpe = [max(t, 1) for t in tpe] + drift = csum - sum(tpe) + if drift: + tpe[tpe.index(max(tpe))] += drift + rng2.shuffle(tpe) + padded_each = [math.ceil(t / pad) * pad for t in tpe] + bf_plan.append({ + "tpe": tpe, "padded_each": padded_each, + "padded_N": sum(padded_each), + "input_N": int(round(csum / divisor)), + }) + + fp8_i = bf16_i = 0 + changed = 0 + for p_idx, u_idx in blocks: + parsed = _parse_permute(skeleton[p_idx]) + if parsed is None: + continue + variant, old_input_N, old_tpe = parsed + if variant == 'fp8': + plan = moe_plan[fp8_i % len(moe_plan)]; fp8_i += 1 + else: + plan = bf_plan[bf16_i % len(bf_plan)]; bf16_i += 1 + # permute 行里 input_N 出现在多个 Tensor 首维([N,7168][N,56][N,4])。 + # 用 scalar_map 只替换 == old_input_N 的 token;7168 等常量绝不入表。 + line = _rewrite_permute_line(skeleton[p_idx], variant, + plan["input_N"], plan["tpe"]) + sm = {} + if old_input_N != plan["input_N"] and old_input_N not in MODEL_CONSTANTS: + sm[old_input_N] = plan["input_N"] + line = _rewrite_int_run(line, sm) + if line != skeleton[p_idx]: + derived[p_idx] = line + changed += 1 + + # ── 第 2 类:其余 MoE 缓冲行逐行局部缩放(不分 block,扫全文件一次) ── + changed += _scale_moe_buffer_lines(derived, skeleton, scale128, pad) + changed += _scale_unpermute_lines(derived, skeleton, scale128) + return changed + + +# 携带 padded-buffer 维(数据相关、随 seq 线性增长)的算子。其首维 N 与 slice 边界 +# 需随 seq 缩放;moe_permute / moe_unpermute 已在第 1 类单独精确处理,这里跳过。 +# 缓冲维有两种书写:① Tensor(Size([N, DIM])) ② list[N, DIM,](empty/reshape/slice 的形状参数)。 +_BUFFER_SHAPE = re.compile(r'Size\(\[(\d+), (7168|3584|56|28)\]') +# list[N,DIM,] 形式:DIM 为 MoE 维时 N=buffer。注意只匹配「DIM 在第二位」的二维形状, +# 避免误伤 list[20,56,56,] 这类常量三维形状(其首维 20 是 num_experts 常量)。 +_BUFFER_LIST = re.compile(r'list\[(\d+),(7168|3584|56|28),\]') +_BUFFER_HEADS = ( + 'paddle.Tensor.__getitem__', 'paddle.Tensor.zero_', 'paddle.assign', + 'paddle.slice', 'paddle.empty_like', 'paddle.Tensor.reshape', + 'paddle.incubate.nn.functional.fused_linear', 'paddle.zeros_like', + 'paddle.Tensor.cast', 'paddle.incubate.nn.functional.fp8_quant_blockwise', + 'paddle._C_ops._run_custom_op', 'paddle.transpose', 'paddle.Tensor.clone', + 'paddle.Tensor.detach', 'paddle.reshape', 'paddle.nn.functional.linear', + 'paddle.incubate.nn.functional.fused_act_dequant', 'paddle.empty', + 'paddle.Tensor.unsqueeze', 'paddle.Tensor.contiguous', +) +# 首维为这些值的是结构常量张量([20,7168,7168]、[1120,56]、[3584,7168] 等),首维不缩放。 +# 3584=hidden/2、7168=hidden、102400=vocab 分片、14336=2·hidden、64/192/2056 等是固定结构维。 +_CONST_FIRST_DIM = frozenset({ + 20, 1120, 143360, 3584, 7168, 14336, 102400, 64, 192, 160, 256, 512, 2056, +}) + + +def _scale_moe_buffer_lines(derived, skeleton, scale128, pad): + """逐行把 MoE padded-buffer 维 N 与 slice 起止按全局比例缩放、对齐 128。 + 以原始骨架为基准识别旧值,写回 derived。返回改写行数。 + + 仅当行内 buffer 维 N ∈ MOE_PADDED_N(真实 padded buffer 集合)才缩放, + 从而不碰常量张量的首维(如 [3584,7168]、[102400,7168])。""" + changed = 0 + for i, base in enumerate(skeleton): + head = base.split('(', 1)[0] + if head not in _BUFFER_HEADS: + continue + sm = _BUFFER_SHAPE.search(base) + lm = _BUFFER_LIST.search(base) + if not (sm or lm): + continue + new_line = base + new_N = None + # ① Tensor(Size([N,DIM])):N 必须是真实 padded buffer 才缩放 + if sm: + old_N = int(sm.group(1)) + if old_N in MOE_PADDED_N and old_N not in _CONST_FIRST_DIM: + new_N = scale128(old_N, "ceil") + # slice(若有)随之缩放并夹紧 + sl = SLICE_RE.search(base) + if sl: + a, b = int(sl.group(1)), int(sl.group(2)) + na = min(scale128(a, "floor"), new_N) + nb = min(scale128(b, "ceil"), new_N) + if na > nb: + na = nb + new_line = (new_line[:sl.start()] + + f'slice({na},{nb},None)' + new_line[sl.end():]) + new_line = _BUFFER_SHAPE.sub( + lambda m: m.group(0).replace( + f'[{m.group(1)}, ', f'[{new_N}, ', 1), + new_line, count=1) + # ② list[N,DIM,]:empty/reshape/slice 的形状参数(N=行数, DIM∈MoE 维)。 + # 歧义点:N==7168 时既可能是 padded buffer(empty 的行数),也可能是权重 reshape + # 的行数(list[7168,7168,] 来自 flat[51380224]=7168² 的方阵权重)。靠算子区分: + # - paddle.empty(...) → N 永远是 buffer 行数 → 用含 7168 的 RAW 集合 + # - reshape/slice/其它形状参数 → 可能是权重 → 用剔除常量的 MOE_PADDED_N 集合 + if lm: + old_LN = int(lm.group(1)) + allow = MOE_PADDED_N_RAW if head == 'paddle.empty' else MOE_PADDED_N + if old_LN in allow: + nLN = scale128(old_LN, "ceil") + new_line = _BUFFER_LIST.sub( + lambda m: f'list[{nLN},{m.group(2)},]', new_line, count=1) + if new_line != base: + derived[i] = new_line + changed += 1 + return changed + + +# moe_unpermute 行结构: +# moe_unpermute(Tensor([padded_N,7168]), Tensor([input_N,20]), Tensor([input_N,4]), +# Tensor([padded_N]), input_N, 20, ) +# 其中 7168/20/4 是常量,padded_N 与 input_N 随 seq 线性增长,须缩放。 +_UNPERM_RE = re.compile( + r'(moe_unpermute\(Tensor\(paddle\.Size\(\[)(\d+)(, 7168\]\),"bfloat16"\), ' + r'Tensor\(paddle\.Size\(\[)(\d+)(, 20\]\),"int32"\), ' + r'Tensor\(paddle\.Size\(\[)(\d+)(, 4\]\),"int32"\), ' + r'Tensor\(paddle\.Size\(\[)(\d+)(\]\),"float32"\), )(\d+)(, 20, \))') + + +def _scale_unpermute_lines(derived, skeleton, scale128): + """缩放 moe_unpermute 行的 padded_N(首维与第4个张量)与 input_N(第2/3张量及尾参)。""" + changed = 0 + for i, base in enumerate(skeleton): + if 'moe_unpermute' not in base: + continue + m = _UNPERM_RE.search(base) + if not m: + continue + old_pad = int(m.group(2)) # padded_N + old_in = int(m.group(4)) # input_N + new_pad = scale128(old_pad, "ceil") + new_in = scale128(old_in, "round") + new_line = _UNPERM_RE.sub( + lambda mm: (mm.group(1) + str(new_pad) + mm.group(3) + str(new_in) + + mm.group(5) + str(new_in) + mm.group(7) + str(new_pad) + + mm.group(9) + str(new_in) + mm.group(11)), + base, count=1) + if new_line != base: + derived[i] = new_line + changed += 1 + return changed + +def parse_args(): + parser = argparse.ArgumentParser( + description="API config 推导脚本:由两个不同 seq 的配置推导任意目标 seq(含 MoE 结构重建)。", + ) + parser.add_argument("target_seq", type=int, + help="目标 sequence 长度(必须是 seq_small 的整数倍)") + parser.add_argument("--small", default=None, + help="小 seq 配置文件路径(默认:脚本目录下 api_config_.txt)") + parser.add_argument("--large", default=None, + help="大 seq 配置文件路径(默认:脚本目录下 api_config_.txt)") + parser.add_argument("--seq-small", type=int, default=1024, + help="小配置的 seq 值(默认:1024)") + parser.add_argument("--seq-large", type=int, default=2048, + help="大配置的 seq 值(默认:2048)") + parser.add_argument("-o", "--output", default=None, + help="输出文件路径(默认:源文件同目录下 api_config_derived_.txt)") + parser.add_argument("--seed", type=int, default=20240528, + help="MoE 重建随机种子(默认:20240528)") + parser.add_argument("--jitter", type=float, default=0.04, + help="MoE 重采样抖动幅度(默认:0.04)") + return parser.parse_args() + + +def main(): + global SEQ_SMALL, SEQ_LARGE, SEQ_TARGET, MULT, RANDOM_SEED, JITTER + + args = parse_args() + + SEQ_SMALL = args.seq_small + SEQ_LARGE = args.seq_large + SEQ_TARGET = args.target_seq + RANDOM_SEED = args.seed + JITTER = args.jitter + + assert (SEQ_TARGET - SEQ_SMALL) % SEQ_SMALL == 0, \ + f"TARGET ({SEQ_TARGET}) 必须满足 (TARGET - {SEQ_SMALL}) % {SEQ_SMALL} == 0" + MULT = (SEQ_TARGET - SEQ_SMALL) // SEQ_SMALL + + file_small = args.small or os.path.join(BASE_DIR, f"api_config_{SEQ_SMALL}.txt") + file_large = args.large or os.path.join(BASE_DIR, f"api_config_{SEQ_LARGE}.txt") + + if args.output: + output_path = args.output + else: + out_dir = os.path.dirname(file_large) or "." + output_path = os.path.join(out_dir, f"api_config_derived_{SEQ_TARGET}.txt") + + output_dir = os.path.dirname(output_path) + if output_dir: + os.makedirs(output_dir, exist_ok=True) + + if not os.path.exists(file_small): + print(f"错误:源文件缺失 {file_small}") + sys.exit(1) + if not os.path.exists(file_large): + print(f"错误:源文件缺失 {file_large}") + sys.exit(1) + + print(f"推导: seq {SEQ_SMALL}+{SEQ_LARGE} → {SEQ_TARGET} " + f"(确定类仿射 mult={MULT}; MoE 约束重采样, seed={RANDOM_SEED})") + print("=" * 70) + + lines_small = [l.rstrip('\n') for l in open(file_small)] + lines_large = [l.rstrip('\n') for l in open(file_large)] + print(f" seq={SEQ_SMALL}: {len(lines_small)} 行") + print(f" seq={SEQ_LARGE}: {len(lines_large)} 行 (作为输出骨架)") + + vmap = build_value_map(lines_small, lines_large) + learned = sum(len(d) for d in vmap.values()) + print(f" 学到仿射映射: {len(vmap)} 个(签名,位置), 共 {learned} 条值映射") + + profile = load_profile() + moe_plan = build_moe_plan(profile) + print(f" MoE 重建计划: {len(moe_plan)} 个 moe_permute 调用, " + f"总 token 预算 {profile['moe_permute_calls'] * SEQ_TARGET * 4}") + + # 第一遍:确定类仿射改写(MoE block 内的行随后被结构重建覆盖) + derived = [derive_line(l, vmap) for l in lines_large] + + # 第二遍:MoE 结构重建。 + moe_changed = apply_moe_reconstruction(derived, lines_large, moe_plan, profile) + + affine_changed = sum(1 for a, b in zip(derived, lines_large) if a != b) + + with open(output_path, 'w') as f: + f.write('\n'.join(derived) + '\n') + + print("=" * 70) + print(f"完成: 共 {len(derived)} 行") + print(f" 改写行数(含 MoE): {affine_changed}") + print(f" MoE 结构重建行数: {moe_changed}") + print(f"输出: {output_path}") + + +if __name__ == "__main__": + main() diff --git a/tools/qa_test/extract_api_set.py b/tools/qa_test/extract_api_set.py new file mode 100644 index 00000000..baa0a1a1 --- /dev/null +++ b/tools/qa_test/extract_api_set.py @@ -0,0 +1,101 @@ +# 提取 API 名集合小工具 +# @author: cangtianhuang +# @date: 2026-06-11 + +from __future__ import annotations + +import argparse +from pathlib import Path + + +def collect_input_files(input_paths): + files = [] + for input_path in input_paths: + path = Path(input_path) + if path.is_file(): + files.append(path) + elif path.is_dir(): + text_files = list(path.rglob("*.txt")) + files.extend(text_files) + print(f"Found {len(text_files)} .txt files in directory: {path}") + else: + print(f"Warning: {path} does not exist or is not accessible") + return files + + +def extract_apis(input_paths, output_file): + input_files = collect_input_files(input_paths) + if not input_files: + print("No valid input files found") + return + + print(f"Processing {len(input_files)} files...") + + api_names = set() + total_processed = 0 + + for input_file in input_files: + try: + content = input_file.read_text(encoding="utf-8") + file_count = 0 + + for line in content.splitlines(): + line = line.strip() + if line and "(" in line: + api_name = line.split("(", 1)[0].strip() + if api_name: + api_names.add(api_name) + file_count += 1 + total_processed += 1 + + print(f"Processed {file_count} APIs from {input_file}") + except Exception as err: + print(f"Error reading {input_file}: {err}") + continue + + if not api_names: + print("No valid APIs found") + return + + print(f"Total processed: {total_processed}, Unique APIs: {len(api_names)}") + + output_path = Path(output_file) + output_path.parent.mkdir(parents=True, exist_ok=True) + sorted_apis = sorted(api_names) + + output_path.write_text("\n".join(sorted_apis) + "\n", encoding="utf-8") + print(f"Wrote {len(sorted_apis)} API names to {output_path}") + + +def parse_args(argv=None): + parser = argparse.ArgumentParser( + description="API 提取工具:从配置文件中提取唯一 API 名称集合。", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +使用示例: + python %(prog)s -i config.txt -o api_extracted.txt + python %(prog)s -i configs/ -o /output/dir/apis.txt + python %(prog)s -i file1.txt file2.txt -o result.txt + """, + ) + parser.add_argument( + "-i", "--input", + nargs="+", + required=True, + help="输入路径列表(支持文件或目录)", + ) + parser.add_argument( + "-o", "--output", + default="api_extracted.txt", + help="输出文件路径(默认:当前目录下 api_extracted.txt)", + ) + return parser.parse_args(argv) + + +def main(argv=None): + args = parse_args(argv) + extract_apis(args.input, args.output) + + +if __name__ == "__main__": + main() diff --git a/tools/qa_test/merge_configs.py b/tools/qa_test/merge_configs.py new file mode 100644 index 00000000..78e49e75 --- /dev/null +++ b/tools/qa_test/merge_configs.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +"""合并多个 txt 配置文件,剔除空行。 + +Usage: + python merge_configs.py -i dir1/ dir2/ + python merge_configs.py -i file1.txt file2.txt file3.txt + python merge_configs.py -i dir1/ file2.txt -o /output/merged.txt +""" +import argparse +import glob +import os + + +def parse_args(): + parser = argparse.ArgumentParser( + description="合并多个 txt 配置文件,剔除空行。", + ) + parser.add_argument("-i", "--inputs", nargs="+", required=True, + help="输入路径(目录或文件),可指定多个") + parser.add_argument("-o", "--output", default=None, + help="输出文件路径。默认:第一个输入目录下 merged.txt") + return parser.parse_args() + + +def collect_files(inputs): + """从输入列表收集所有 txt 文件路径。""" + files = [] + for path in inputs: + if os.path.isdir(path): + files.extend(sorted(glob.glob(os.path.join(path, "*.txt")))) + elif os.path.isfile(path): + files.append(path) + else: + print(f"警告:路径不存在,跳过 {path}") + return files + + +def main(): + args = parse_args() + + if args.output: + output_file = args.output + else: + first_input = args.inputs[0] + if os.path.isdir(first_input): + output_file = os.path.join(first_input, "merged.txt") + else: + output_file = os.path.join(os.path.dirname(first_input) or ".", "merged.txt") + + output_dir = os.path.dirname(output_file) + if output_dir: + os.makedirs(output_dir, exist_ok=True) + + txt_files = collect_files(args.inputs) + output_abs = os.path.abspath(output_file) + + lines = [] + for filepath in txt_files: + if os.path.abspath(filepath) == output_abs: + continue + with open(filepath, "r") as f: + for line in f: + stripped = line.strip() + if stripped: + lines.append(stripped) + + with open(output_file, "w") as f: + f.write("\n".join(lines) + "\n") + + print(f"合并完成,共 {len(lines)} 行,输出到 {output_file}") + + +if __name__ == "__main__": + main() diff --git a/tools/qa_test/run_pipeline.sh b/tools/qa_test/run_pipeline.sh new file mode 100755 index 00000000..df087118 --- /dev/null +++ b/tools/qa_test/run_pipeline.sh @@ -0,0 +1,162 @@ +#!/bin/bash +# ============================================================================ +# API Config 全流程处理脚本 +# +# 从原始 api_config_*.txt 出发,推导1M、验证、生成0size、合并去重、提取API名。 +# +# 用法: +# bash run_pipeline.sh -i <输入目录> -o <输出目录> +# +# 示例: +# bash run_pipeline.sh -i api_config_0703 -o api_config_dedup_0703 +# +# 最终输出: +# api_config_1M_paddleonly.txt - 原始配置 + 推导1M,合并去重 +# api_config_0size_paddleonly.txt - 0size 配置去重 +# 1M_api_extracted.txt - 从上面提取的 API 名集合 +# 0size_api_extracted.txt - 从上面提取的 API 名集合 +# ============================================================================ + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# ─── 参数解析 ─── +INPUT_DIR="" +OUTPUT_DIR="" + +while getopts "i:o:h" opt; do + case $opt in + i) INPUT_DIR="$OPTARG" ;; + o) OUTPUT_DIR="$OPTARG" ;; + h) + echo "用法: bash $0 -i <输入目录> -o <输出目录>" + echo " -i 输入目录(包含 api_config_*.txt)" + echo " -o 输出目录" + exit 0 + ;; + \?) echo "无效选项: -$OPTARG" >&2; exit 1 ;; + esac +done + +if [ -z "$INPUT_DIR" ] || [ -z "$OUTPUT_DIR" ]; then + echo "错误:必须指定 -i(输入目录)和 -o(输出目录)" + echo "用法: bash $0 -i <输入目录> -o <输出目录>" + exit 1 +fi + +INPUT_DIR="$(cd "$INPUT_DIR" && pwd)" +mkdir -p "$OUTPUT_DIR" +OUTPUT_DIR="$(cd "$OUTPUT_DIR" && pwd)" + +echo "======================================================================" +echo "API Config 全流程处理" +echo " 输入: $INPUT_DIR" +echo " 输出: $OUTPUT_DIR" +echo "======================================================================" + +# ─── 检查输入 ─── +if [ ! -f "$INPUT_DIR/api_config_1024.txt" ] || [ ! -f "$INPUT_DIR/api_config_2048.txt" ]; then + echo "错误:输入目录需要至少包含 api_config_1024.txt 和 api_config_2048.txt" + exit 1 +fi + +# ============================================================================ +# Step 1: 推导虚假 4096 并验证(如果有真实 4096) +# ============================================================================ +echo "" +echo "[Step 1] 推导 4096 并验证..." + +python "$SCRIPT_DIR/derive_api_seq.py" 4096 \ + --small "$INPUT_DIR/api_config_1024.txt" \ + --large "$INPUT_DIR/api_config_2048.txt" \ + -o "$OUTPUT_DIR/api_config_derived_4096.txt" + +if [ -f "$INPUT_DIR/api_config_4096.txt" ]; then + echo "" + python "$SCRIPT_DIR/verify_api_seq.py" \ + -d "$OUTPUT_DIR/api_config_derived_4096.txt" \ + -r "$INPUT_DIR/api_config_4096.txt" +else + echo " [跳过验证] 未找到真实 api_config_4096.txt" +fi + +# ============================================================================ +# Step 2: 推导 1M +# ============================================================================ +echo "" +echo "[Step 2] 推导 1M (seq=1048576)..." + +python "$SCRIPT_DIR/derive_api_seq.py" 1048576 \ + --small "$INPUT_DIR/api_config_1024.txt" \ + --large "$INPUT_DIR/api_config_2048.txt" \ + -o "$OUTPUT_DIR/api_config_1M.txt" + +# ============================================================================ +# Step 3: 合并原始配置 + 1M,去重 → api_config_1M_paddleonly.txt +# ============================================================================ +echo "" +echo "[Step 3] 合并 + 去重 → api_config_1M_paddleonly.txt..." + +# 合并:原始所有 txt + 推导的 1M +python "$SCRIPT_DIR/merge_configs.py" \ + -i "$INPUT_DIR" "$OUTPUT_DIR/api_config_1M.txt" \ + -o "$OUTPUT_DIR/_tmp_merged.txt" + +# 去重 +python "$SCRIPT_DIR/dedup_config.py" \ + -i "$OUTPUT_DIR/_tmp_merged.txt" \ + -o "$OUTPUT_DIR/api_config_1M_paddleonly.txt" + +rm -f "$OUTPUT_DIR/_tmp_merged.txt" + +# ============================================================================ +# Step 4: 生成 0size 配置,去重 → api_config_0size_paddleonly.txt +# ============================================================================ +echo "" +echo "[Step 4] 生成 0-size + 去重 → api_config_0size_paddleonly.txt..." + +# 收集所有输入:原始 + 1M +ZERO_INPUTS="" +for f in "$INPUT_DIR"/api_config_*.txt; do + ZERO_INPUTS="$ZERO_INPUTS $f" +done +ZERO_INPUTS="$ZERO_INPUTS $OUTPUT_DIR/api_config_1M.txt" + +python "$SCRIPT_DIR/to_0_size_config.py" \ + -i $ZERO_INPUTS \ + -o "$OUTPUT_DIR/_tmp_0size.txt" + +# 去重 +python "$SCRIPT_DIR/dedup_config.py" \ + -i "$OUTPUT_DIR/_tmp_0size.txt" \ + -o "$OUTPUT_DIR/api_config_0size_paddleonly.txt" + +rm -f "$OUTPUT_DIR/_tmp_0size.txt" + +# ============================================================================ +# Step 5: 提取 API 名集合 +# ============================================================================ +echo "" +echo "[Step 5] 提取 API 名称集合..." + +python "$SCRIPT_DIR/extract_api_set.py" \ + -i "$OUTPUT_DIR/api_config_1M_paddleonly.txt" \ + -o "$OUTPUT_DIR/1M_api_extracted.txt" + +python "$SCRIPT_DIR/extract_api_set.py" \ + -i "$OUTPUT_DIR/api_config_0size_paddleonly.txt" \ + -o "$OUTPUT_DIR/0size_api_extracted.txt" + +# ============================================================================ +# 清理中间文件,只保留最终 4 个 +# ============================================================================ +rm -f "$OUTPUT_DIR/api_config_derived_4096.txt" +rm -f "$OUTPUT_DIR/api_config_1M.txt" + +echo "" +echo "======================================================================" +echo "完成!输出目录: $OUTPUT_DIR" +echo "======================================================================" +echo "" +ls -la "$OUTPUT_DIR" diff --git a/tools/qa_test/to_0_size_config.py b/tools/qa_test/to_0_size_config.py new file mode 100644 index 00000000..8ba4241e --- /dev/null +++ b/tools/qa_test/to_0_size_config.py @@ -0,0 +1,310 @@ +from __future__ import annotations + +import argparse +import copy +import math +import os + +import numpy +import paddle +from config_analyzer import TensorConfig, analyse_configs +from tqdm import tqdm + + +def is_0_size_tensor(tensor_config): + return any(i == 0 for i in tensor_config.shape) + + +def is_0D_tensor(tensor_config): + return len(tensor_config.shape) == 0 + + +def tensor_numel(tensor_config): + numel = 1 + for i in tensor_config.shape: + numel = numel * i + return numel + + +def get_tensor_configs(api_config): + tensor_configs = [] + for arg_config in api_config.args: + if isinstance(arg_config, TensorConfig): + tensor_configs.append(arg_config) + elif isinstance(arg_config, (list, tuple)): + for j in range(len(arg_config)): + if isinstance(arg_config[j], TensorConfig): + tensor_configs.append(arg_config[j]) + + for _key, arg_config in api_config.kwargs.items(): + if isinstance(arg_config, TensorConfig): + tensor_configs.append(arg_config) + elif isinstance(arg_config, (list, tuple)): + for j in range(len(arg_config)): + if isinstance(arg_config[j], TensorConfig): + tensor_configs.append(arg_config[j]) + return tensor_configs + + +def to_0_size_config(api_config): + if api_config.api_name not in apis_map: + apis_map[api_config.api_name] = {} + + key = config_key(api_config) + + if key not in apis_map[api_config.api_name]: + apis_map[api_config.api_name][key] = 1 + else: + apis_map[api_config.api_name][key] += 1 + + if apis_map[api_config.api_name][key] > 5: + return [] + + result = [] + tensor_configs = get_tensor_configs(api_config) + + if len(tensor_configs) == 0: + return [] + + shape_len = len(tensor_configs[0].shape) + shape_equal = True + for tensor_config in tensor_configs: + if is_0_size_tensor(tensor_config) or is_0D_tensor(tensor_config): + return [] + if shape_len != len(tensor_config.shape): + shape_equal = False + + for i in range(len(tensor_configs)): + for j in range(len(tensor_configs[i].shape)): + tmp_api_config = copy.deepcopy(api_config) + tmp_tensor_configs = get_tensor_configs(tmp_api_config) + tmp_tensor_configs[i].shape[j] = 0 + result.append(str(tmp_api_config)) + + if shape_equal: + for j in range(shape_len): + tmp_api_config = copy.deepcopy(api_config) + tmp_tensor_configs = get_tensor_configs(tmp_api_config) + for i in range(len(tensor_configs)): + tmp_tensor_configs[i].shape[j] = 0 + result.append(str(tmp_api_config)) + return result + + +apis_map = {} + + +def dump_item_str(item): + type_mapping = { + numpy.int16: int, + numpy.int32: int, + numpy.int64: int, + numpy.float16: float, + numpy.float32: float, + numpy.float64: float, + numpy.integer: int, + numpy.floating: float, + numpy.bool_: bool, + numpy.complexfloating: complex, + numpy.str_: str, + numpy.bytes_: bytes, + # numpy.unicode_: str, + } + for numpy_type, builtin_type in type_mapping.items(): + if isinstance(item, numpy_type): + item = builtin_type(item) + break + + if isinstance(item, TensorConfig): + return "Tensor(" + str(len(item.shape)) + ")" + elif isinstance(item, paddle.base.core.DataType): + return "Dtype(" + str(item)[7:] + ")" + elif isinstance(item, paddle.base.core.VarDesc.VarType): + return "VarType(" + str(item)[7:] + ")" + elif isinstance(item, list): + result = "list[" + for sub_item in item: + tmp = dump_item_str(sub_item) + if tmp == "": + return "" + result = result + tmp + "," + result = result + "]" + return result + elif isinstance(item, tuple): + result = "tuple(" + for sub_item in item: + tmp = dump_item_str(sub_item) + if tmp == "": + return "" + result = result + tmp + "," + result = result + ")" + return result + elif isinstance(item, slice): + return "slice(" + str(item.start) + "," + str(item.stop) + "," + str(item.step) + ")" + elif isinstance(item, complex): + return "complex(" + dump_item_str(item.real) + "," + dump_item_str(item.imag) + ")" + elif item is None: + return "None" + elif isinstance(item, (paddle.base.Variable, paddle.base.libpaddle.pir.Value)): + return "" + elif item == math.inf: + return "math.inf" + elif item == -math.inf: + return "-math.inf" + elif item == math.nan: + return "math.nan" + elif item == -math.nan: + return "-math.nan" + elif isinstance(item, (bool, int, float)): + return str(item) + elif isinstance(item, str): + return '"' + item + '"' + elif isinstance(item, type): + return "type(" + str(item)[str(item).index("'") + 1 : str(item).rindex("'")] + ")" + elif callable(item): + name = getattr(item, '__name__', None) or getattr(item, '__qualname__', None) + if name: + return "callable(" + name + ")" + return "callable(unknown)" + else: + return str(item) + + +def config_key(api_config): + result = "" + for arg in api_config.args: + result = result + dump_item_str(arg) + ", " + + for key, value in api_config.kwargs.items(): + result = result + key + "=" + dump_item_str(value) + ", " + + return result + + +def to_big_tensor_config(api_config): + if api_config.api_name not in apis_map: + apis_map[api_config.api_name] = {} + + key = config_key(api_config) + + if key not in apis_map[api_config.api_name]: + apis_map[api_config.api_name][key] = 1 + else: + apis_map[api_config.api_name][key] += 1 + + if apis_map[api_config.api_name][key] > 5: + return [] + + tensor_configs = get_tensor_configs(api_config) + + result = [] + + if len(tensor_configs) == 0: + return [] + + shape_len = len(tensor_configs[0].shape) + shape_equal = True + for tensor_config in tensor_configs: + if is_0_size_tensor(tensor_config) or is_0D_tensor(tensor_config): + return [] + if tensor_config.dtype in ["complex64", "complex128"]: + return [] + if shape_len != len(tensor_config.shape): + shape_equal = False + + for i in range(len(tensor_configs)): + for j in range(len(tensor_configs[i].shape)): + tmp_api_config = copy.deepcopy(api_config) + tmp_tensor_configs = get_tensor_configs(tmp_api_config) + if tmp_tensor_configs[i].dtype in [ + "float8", + "float16", + "bfloat16", + "int16", + "uint16", + "int8", + "uint8", + ]: + base_size = 4294967296 + elif tmp_tensor_configs[i].dtype in ["float64"]: + base_size = 4294967296 + for k in range(len(tmp_tensor_configs)): + if tmp_tensor_configs[k].dtype in ["float64"]: + tmp_tensor_configs[k].dtype = "float16" + else: + base_size = 2281701378 + tmp_tensor_configs[i].shape[j] = ( + int( + base_size + / (tensor_numel(tmp_tensor_configs[i]) / tmp_tensor_configs[i].shape[j]) + ) + + 1 + ) + config_str = str(tmp_api_config) + if len(config_str) < 1000: + result.append(config_str) + + if shape_equal: + for j in range(shape_len): + tmp_api_config = copy.deepcopy(api_config) + tmp_tensor_configs = get_tensor_configs(tmp_api_config) + for i in range(len(tensor_configs)): + if tmp_tensor_configs[i].dtype in [ + "float8", + "float16", + "bfloat16", + "int16", + "uint16", + "int8", + "uint8", + ]: + base_size = 4294967296 + elif tmp_tensor_configs[i].dtype in ["float64"]: + base_size = 4294967296 + tmp_tensor_configs[i].dtype = "float16" + else: + base_size = 2281701378 + tmp_tensor_configs[i].shape[j] = ( + int( + base_size + / (tensor_numel(tmp_tensor_configs[0]) / tmp_tensor_configs[0].shape[j]) + ) + + 1 + ) + config_str = str(tmp_api_config) + if len(config_str) < 1000: + result.append(config_str) + return result + + +def parse_args(): + parser = argparse.ArgumentParser( + description="将 API 配置转换为 0-size tensor 变体,用于边界测试。", + ) + parser.add_argument("-i", "--inputs", nargs="+", required=True, + help="输入配置文件路径(可指定多个)") + parser.add_argument("-o", "--output", default="api_config_0_size.txt", + help="输出文件路径(默认:当前目录下 api_config_0_size.txt)") + return parser.parse_args() + + +if __name__ == "__main__": + args = parse_args() + + output_dir = os.path.dirname(args.output) + if output_dir: + os.makedirs(output_dir, exist_ok=True) + + config_0_size = set() + for input_file in args.inputs: + print(f"处理: {input_file}") + api_configs = analyse_configs(input_file) + config_0_size_chunk = [] + for api_config in tqdm(api_configs): + config_0_size_chunk.extend(set(to_0_size_config(api_config))) + config_0_size = config_0_size.union(set(config_0_size_chunk)) + + with open(args.output, "w") as f: + f.write("\n".join(config_0_size)) + + print(f"输出: {args.output},共 {len(config_0_size)} 行") \ No newline at end of file diff --git a/tools/qa_test/verify_api_seq.py b/tools/qa_test/verify_api_seq.py new file mode 100644 index 00000000..b70cb4bf --- /dev/null +++ b/tools/qa_test/verify_api_seq.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +""" +验证脚本:对比推导出的配置 与 真实抓取的配置,量化推导准确率。 + +文件名:verify_api_seq.py +用法: + python verify_api_seq.py -d api_config_derived_4096.txt -r api_config_4096.txt + +────────────────────────────── 为什么用多重集对比 ────────────────────────────── +配置里约 1/3 的行是 MoE dispatch(router 数据相关),其出现顺序在不同运行间 +是随机的。因此「逐行按位置对比」会被顺序差异严重低估(实测仅 ~18%)。 +真正有意义的指标是「多重集(Counter)重叠」:忽略顺序,看两边相同的行各有多少条。 + +脚本输出三档准确率: + - 整体 :全部行的多重集重叠 + - 确定类(可推导) :剔除 MoE/数据相关行后的多重集重叠(这是推导脚本真正负责的部分) + - MoE 类(不可推导):仅 MoE 行的多重集重叠(作为对照,理论上限很低) + +并打印 Top 不匹配行,便于定位推导脚本的薄弱点。 +""" + +import argparse +import os +import sys +from collections import Counter + + +# 判定「数据相关、不可确定性推导」的行:MoE 相关算子 + 张量取片 +MOE_MARKERS = ( + 'moe_permute', 'moe_unpermute', 'fp8_quant', + 'fused_act_dequant', 'fused_linear', '_run_custom_op', 'swiglu', +) + + +def is_deterministic(line): + """确定类行 = 非 MoE 算子 且 非 __getitem__ 取片。""" + if line.startswith('paddle.Tensor.__getitem__'): + return False + return not any(m in line for m in MOE_MARKERS) + + +def load(path): + with open(path) as f: + return [l.rstrip('\n') for l in f] + + +def overlap(counter_a, counter_b): + """两个多重集的交集元素总数。""" + return sum((counter_a & counter_b).values()) + + +def report(name, derived_lines, real_lines): + cd, cr = Counter(derived_lines), Counter(real_lines) + inter = overlap(cd, cr) + denom = len(real_lines) + pct = inter * 100 / denom if denom else 0.0 + print(f" {name:<14}: {inter}/{denom} = {pct:.2f}% " + f"(推导 {len(derived_lines)} 行 / 真实 {denom} 行)") + return cd, cr + + +def parse_args(): + parser = argparse.ArgumentParser( + description="对比推导配置与真实配置,量化推导准确率(多重集重叠)。", + ) + parser.add_argument("-d", "--derived", required=True, + help="推导的配置文件路径") + parser.add_argument("-r", "--real", required=True, + help="真实/基准配置文件路径") + return parser.parse_args() + + +def main(): + args = parse_args() + + derived_path = args.derived + real_path = args.real + + if not os.path.exists(derived_path): + print(f"错误:推导文件不存在 {derived_path}") + sys.exit(1) + if not os.path.exists(real_path): + print(f"错误:真实文件不存在 {real_path}") + sys.exit(1) + + derived = load(derived_path) + real = load(real_path) + + print(f"验证对比") + print(f" 推导文件: {derived_path}") + print(f" 真实文件: {real_path}") + print("=" * 70) + + # 三档统计 + print("[多重集重叠准确率]") + report("整体", derived, real) + + det_d = [l for l in derived if is_deterministic(l)] + det_r = [l for l in real if is_deterministic(l)] + cd_det, cr_det = report("确定类(可推导)", det_d, det_r) + + moe_d = [l for l in derived if not is_deterministic(l)] + moe_r = [l for l in real if not is_deterministic(l)] + report("MoE类(不可推导)", moe_d, moe_r) + + print("=" * 70) + # 逐行位置对比(仅供参考,受 MoE 顺序随机影响) + n = min(len(derived), len(real)) + exact = sum(1 for i in range(n) if derived[i] == real[i]) + print(f"[逐行位置精确匹配(仅参考)]: {exact}/{n} = {exact*100/n:.2f}%") + + # 确定类的薄弱点 + miss = cd_det - cr_det # 推导出了、真实没有(推多/推错) + extra = cr_det - cd_det # 真实有、推导漏了(推少/推错) + if miss or extra: + print("\n[确定类未匹配 Top(定位推导薄弱点)]") + if miss: + print(" 推导多出/推错的行:") + for line, cnt in miss.most_common(8): + print(f" x{cnt}: {line[:95]}") + if extra: + print(" 真实有但推导缺失的行:") + for line, cnt in extra.most_common(8): + print(f" x{cnt}: {line[:95]}") + + +if __name__ == "__main__": + main() From a01814c1887d510131d00e74590e9f9584d34902 Mon Sep 17 00:00:00 2001 From: Zeref996 <825276847@qq.com> Date: Mon, 6 Jul 2026 07:47:12 +0000 Subject: [PATCH 2/3] add QA test tools --- tools/qa_test/config_analyzer.py | 55 +++++++++++++++----------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/tools/qa_test/config_analyzer.py b/tools/qa_test/config_analyzer.py index 045705f3..b78cead6 100644 --- a/tools/qa_test/config_analyzer.py +++ b/tools/qa_test/config_analyzer.py @@ -2946,18 +2946,38 @@ def get_exponent_max(value, dtype_max, default_max=5): if isinstance(num_experts, TensorConfig): num_experts = 32 seqlen = self.shape[0] + # Fetch unzipped seqlen from hidden_states_unzipped (arg0) to + # bound expert_counters so fetch_row never exceeds the valid + # range of unzipped_token_probs / hidden_states_unzipped. + hidden_config = self.get_arg(api_config, 0, "hidden_states_unzipped") + if ( + isinstance(hidden_config, TensorConfig) + and hidden_config.shape + and len(hidden_config.shape) > 0 + ): + unzipped_seqlen = hidden_config.shape[0] + else: + unzipped_seqlen = seqlen # fallback rowmap = numpy.full(self.shape, -1, dtype="int32") if isinstance(routemap_config, TensorConfig): if routemap_config.numpy_tensor is None: routemap_config.get_numpy_tensor(api_config, index=2) if routemap_config.numpy_tensor is not None: - # Build rowmap: for each expert, assign row indices in order + # Build rowmap and keep routemap consistent: every + # non-negative expert in routemap must map to a valid + # fetch_row because moe_unpermute reads token_prob by it. expert_counters = numpy.zeros(num_experts, dtype="int32") + routemap = routemap_config.numpy_tensor for i in range(seqlen): for e in range(num_experts): - if numpy.any(routemap_config.numpy_tensor[i] == e): + positions = numpy.where(routemap[i] == e)[0] + if positions.size == 0: + continue + if expert_counters[e] < unzipped_seqlen: rowmap[i, e] = expert_counters[e] expert_counters[e] += 1 + else: + routemap[i, positions] = -1 self.numpy_tensor = rowmap # token_prob_unzipped (arg3): float32, value in [0, 1] elif self.check_arg(api_config, 3, "token_prob_unzipped"): @@ -3458,11 +3478,6 @@ def dump_item_str(self, item): return '"' + item + '"' elif isinstance(item, type): return "type(" + str(item)[str(item).index("'") + 1 : str(item).rindex("'")] + ")" - elif callable(item): - name = getattr(item, '__name__', None) or getattr(item, '__qualname__', None) - if name: - return "callable(" + name + ")" - return "callable(unknown)" else: return str(item) @@ -3659,12 +3674,6 @@ def get_one_arg(self, token, config, offset): value, offset = self.get_complex(config, offset) elif token == "type": value, offset = self.get_numpy_type(config, offset) - elif token == "callable": - # parse callable(name) format - store as a string marker - start = config.index("(", offset - len(token)) + 1 - end = config.index(")", start) - value = config[start:end] # store the callable name as string - offset = end + 1 elif token == "nan": value = float("nan") elif token is not None and config[offset - len(token) - 1] == '"': @@ -3681,28 +3690,14 @@ def get_one_arg(self, token, config, offset): return value, offset -# def analyse_configs(config_path): -# with open(config_path) as f: -# configs = f.readlines() -# f.close() - -# api_configs = [] -# for config in configs: -# # print(config) -# api_config = APIConfig(config) -# api_configs.append(api_config) -# return api_configs - def analyse_configs(config_path): with open(config_path) as f: configs = f.readlines() + f.close() api_configs = [] for config in configs: - config = config.strip() - if not config or "(" not in config: - continue + # print(config) api_config = APIConfig(config) api_configs.append(api_config) - return api_configs - \ No newline at end of file + return api_configs \ No newline at end of file From 87f740e55d130e2eb1fb7124c395628d45f91dfc Mon Sep 17 00:00:00 2001 From: Zeref996 <825276847@qq.com> Date: Mon, 6 Jul 2026 08:11:57 +0000 Subject: [PATCH 3/3] add QA test tools --- tools/qa_test/config_analyzer.py | 55 ++-- tools/qa_test/dedup_config.py | 16 +- tools/qa_test/derive_api_seq.py | 482 +++++++++++++++++++++++------- tools/qa_test/extract_api_set.py | 6 +- tools/qa_test/merge_configs.py | 15 +- tools/qa_test/to_0_size_config.py | 17 +- tools/qa_test/verify_api_seq.py | 34 ++- 7 files changed, 456 insertions(+), 169 deletions(-) diff --git a/tools/qa_test/config_analyzer.py b/tools/qa_test/config_analyzer.py index b78cead6..1036a3b8 100644 --- a/tools/qa_test/config_analyzer.py +++ b/tools/qa_test/config_analyzer.py @@ -2946,38 +2946,18 @@ def get_exponent_max(value, dtype_max, default_max=5): if isinstance(num_experts, TensorConfig): num_experts = 32 seqlen = self.shape[0] - # Fetch unzipped seqlen from hidden_states_unzipped (arg0) to - # bound expert_counters so fetch_row never exceeds the valid - # range of unzipped_token_probs / hidden_states_unzipped. - hidden_config = self.get_arg(api_config, 0, "hidden_states_unzipped") - if ( - isinstance(hidden_config, TensorConfig) - and hidden_config.shape - and len(hidden_config.shape) > 0 - ): - unzipped_seqlen = hidden_config.shape[0] - else: - unzipped_seqlen = seqlen # fallback rowmap = numpy.full(self.shape, -1, dtype="int32") if isinstance(routemap_config, TensorConfig): if routemap_config.numpy_tensor is None: routemap_config.get_numpy_tensor(api_config, index=2) if routemap_config.numpy_tensor is not None: - # Build rowmap and keep routemap consistent: every - # non-negative expert in routemap must map to a valid - # fetch_row because moe_unpermute reads token_prob by it. + # Build rowmap: for each expert, assign row indices in order expert_counters = numpy.zeros(num_experts, dtype="int32") - routemap = routemap_config.numpy_tensor for i in range(seqlen): for e in range(num_experts): - positions = numpy.where(routemap[i] == e)[0] - if positions.size == 0: - continue - if expert_counters[e] < unzipped_seqlen: + if numpy.any(routemap_config.numpy_tensor[i] == e): rowmap[i, e] = expert_counters[e] expert_counters[e] += 1 - else: - routemap[i, positions] = -1 self.numpy_tensor = rowmap # token_prob_unzipped (arg3): float32, value in [0, 1] elif self.check_arg(api_config, 3, "token_prob_unzipped"): @@ -3478,6 +3458,11 @@ def dump_item_str(self, item): return '"' + item + '"' elif isinstance(item, type): return "type(" + str(item)[str(item).index("'") + 1 : str(item).rindex("'")] + ")" + elif callable(item): + name = getattr(item, "__name__", None) or getattr(item, "__qualname__", None) + if name: + return "callable(" + name + ")" + return "callable(unknown)" else: return str(item) @@ -3674,6 +3659,12 @@ def get_one_arg(self, token, config, offset): value, offset = self.get_complex(config, offset) elif token == "type": value, offset = self.get_numpy_type(config, offset) + elif token == "callable": + # parse callable(name) format - store as a string marker + start = config.index("(", offset - len(token)) + 1 + end = config.index(")", start) + value = config[start:end] # store the callable name as string + offset = end + 1 elif token == "nan": value = float("nan") elif token is not None and config[offset - len(token) - 1] == '"': @@ -3690,14 +3681,28 @@ def get_one_arg(self, token, config, offset): return value, offset +# def analyse_configs(config_path): +# with open(config_path) as f: +# configs = f.readlines() +# f.close() + +# api_configs = [] +# for config in configs: +# # print(config) +# api_config = APIConfig(config) +# api_configs.append(api_config) +# return api_configs + + def analyse_configs(config_path): with open(config_path) as f: configs = f.readlines() - f.close() api_configs = [] for config in configs: - # print(config) + config = config.strip() + if not config or "(" not in config: + continue api_config = APIConfig(config) api_configs.append(api_config) - return api_configs \ No newline at end of file + return api_configs diff --git a/tools/qa_test/dedup_config.py b/tools/qa_test/dedup_config.py index a207c1a2..e59a12df 100644 --- a/tools/qa_test/dedup_config.py +++ b/tools/qa_test/dedup_config.py @@ -5,6 +5,9 @@ python dedup_config.py -i api_config_0_size.txt python dedup_config.py -i api_config_0_size.txt -o /output/dir/dedup.txt """ + +from __future__ import annotations + import argparse import os @@ -13,10 +16,13 @@ def parse_args(): parser = argparse.ArgumentParser( description="Deduplicate configuration lines while preserving sorted unique output.", ) - parser.add_argument("-i", "--input", required=True, - help="Input config file path") - parser.add_argument("-o", "--output", default=None, - help="Output file path. Default: _dedup.txt in same dir as input") + parser.add_argument("-i", "--input", required=True, help="Input config file path") + parser.add_argument( + "-o", + "--output", + default=None, + help="Output file path. Default: _dedup.txt in same dir as input", + ) return parser.parse_args() @@ -36,7 +42,7 @@ def main(): seen = set() total = 0 - with open(input_path, "r", encoding="utf-8") as f: + with open(input_path, encoding="utf-8") as f: for line in f: total += 1 seen.add(line.rstrip("\n")) diff --git a/tools/qa_test/derive_api_seq.py b/tools/qa_test/derive_api_seq.py index cc1e23d1..a0551db0 100644 --- a/tools/qa_test/derive_api_seq.py +++ b/tools/qa_test/derive_api_seq.py @@ -33,14 +33,16 @@ moe_permute、下游 reshape/transpose/getitem 切片、moe_unpermute 各行。 """ +from __future__ import annotations + import argparse -import os -import re -import sys import json import math +import os import random -from collections import defaultdict, Counter +import re +import sys +from collections import Counter, defaultdict # ──────────────────────────── 配置(运行时由 parse_args 填充) ──────────────────────────── @@ -64,8 +66,174 @@ # 总 Σtpe = 144 * seq * 4(每调用均值 = seq*4)。input_N = round(Σtpe/1.2033)。 # padded buffer N = Σ ceil(tpe_e/128)*128。 MOE_PROFILE = { - "expert_profile_20": [0.004221, 0.009402, 0.013823, 0.017920, 0.021618, 0.024818, 0.027641, 0.031208, 0.034247, 0.037616, 0.041644, 0.045602, 0.050711, 0.055962, 0.062604, 0.070290, 0.078967, 0.091881, 0.117033, 0.162790], - "per_call_profile_144": [0.003664, 0.003801, 0.004234, 0.004444, 0.004525, 0.004777, 0.004919, 0.005035, 0.005125, 0.005139, 0.005180, 0.005205, 0.005312, 0.005355, 0.005375, 0.005457, 0.005564, 0.005601, 0.005675, 0.005703, 0.005709, 0.005789, 0.005803, 0.005829, 0.005858, 0.005963, 0.006043, 0.006070, 0.006091, 0.006100, 0.006104, 0.006126, 0.006133, 0.006140, 0.006171, 0.006200, 0.006234, 0.006255, 0.006274, 0.006280, 0.006307, 0.006346, 0.006366, 0.006374, 0.006409, 0.006424, 0.006438, 0.006467, 0.006474, 0.006490, 0.006528, 0.006545, 0.006573, 0.006583, 0.006598, 0.006636, 0.006648, 0.006671, 0.006675, 0.006681, 0.006692, 0.006706, 0.006721, 0.006727, 0.006731, 0.006735, 0.006742, 0.006753, 0.006800, 0.006809, 0.006825, 0.006849, 0.006865, 0.006934, 0.006941, 0.006973, 0.006980, 0.006985, 0.007008, 0.007014, 0.007018, 0.007032, 0.007048, 0.007081, 0.007096, 0.007136, 0.007147, 0.007174, 0.007184, 0.007203, 0.007225, 0.007232, 0.007273, 0.007285, 0.007297, 0.007313, 0.007364, 0.007392, 0.007431, 0.007452, 0.007475, 0.007515, 0.007547, 0.007573, 0.007606, 0.007624, 0.007629, 0.007691, 0.007701, 0.007715, 0.007758, 0.007781, 0.007834, 0.007887, 0.007920, 0.007941, 0.007958, 0.008033, 0.008083, 0.008139, 0.008153, 0.008231, 0.008283, 0.008317, 0.008353, 0.008394, 0.008409, 0.008418, 0.008460, 0.008569, 0.008642, 0.008678, 0.008729, 0.008813, 0.008845, 0.008858, 0.008933, 0.009125, 0.009336, 0.009394, 0.009520, 0.009638, 0.009690, 0.010134], + "expert_profile_20": [ + 0.004221, + 0.009402, + 0.013823, + 0.017920, + 0.021618, + 0.024818, + 0.027641, + 0.031208, + 0.034247, + 0.037616, + 0.041644, + 0.045602, + 0.050711, + 0.055962, + 0.062604, + 0.070290, + 0.078967, + 0.091881, + 0.117033, + 0.162790, + ], + "per_call_profile_144": [ + 0.003664, + 0.003801, + 0.004234, + 0.004444, + 0.004525, + 0.004777, + 0.004919, + 0.005035, + 0.005125, + 0.005139, + 0.005180, + 0.005205, + 0.005312, + 0.005355, + 0.005375, + 0.005457, + 0.005564, + 0.005601, + 0.005675, + 0.005703, + 0.005709, + 0.005789, + 0.005803, + 0.005829, + 0.005858, + 0.005963, + 0.006043, + 0.006070, + 0.006091, + 0.006100, + 0.006104, + 0.006126, + 0.006133, + 0.006140, + 0.006171, + 0.006200, + 0.006234, + 0.006255, + 0.006274, + 0.006280, + 0.006307, + 0.006346, + 0.006366, + 0.006374, + 0.006409, + 0.006424, + 0.006438, + 0.006467, + 0.006474, + 0.006490, + 0.006528, + 0.006545, + 0.006573, + 0.006583, + 0.006598, + 0.006636, + 0.006648, + 0.006671, + 0.006675, + 0.006681, + 0.006692, + 0.006706, + 0.006721, + 0.006727, + 0.006731, + 0.006735, + 0.006742, + 0.006753, + 0.006800, + 0.006809, + 0.006825, + 0.006849, + 0.006865, + 0.006934, + 0.006941, + 0.006973, + 0.006980, + 0.006985, + 0.007008, + 0.007014, + 0.007018, + 0.007032, + 0.007048, + 0.007081, + 0.007096, + 0.007136, + 0.007147, + 0.007174, + 0.007184, + 0.007203, + 0.007225, + 0.007232, + 0.007273, + 0.007285, + 0.007297, + 0.007313, + 0.007364, + 0.007392, + 0.007431, + 0.007452, + 0.007475, + 0.007515, + 0.007547, + 0.007573, + 0.007606, + 0.007624, + 0.007629, + 0.007691, + 0.007701, + 0.007715, + 0.007758, + 0.007781, + 0.007834, + 0.007887, + 0.007920, + 0.007941, + 0.007958, + 0.008033, + 0.008083, + 0.008139, + 0.008153, + 0.008231, + 0.008283, + 0.008317, + 0.008353, + 0.008394, + 0.008409, + 0.008418, + 0.008460, + 0.008569, + 0.008642, + 0.008678, + 0.008729, + 0.008813, + 0.008845, + 0.008858, + 0.008933, + 0.009125, + 0.009336, + 0.009394, + 0.009520, + 0.009638, + 0.009690, + 0.010134, + ], "input_N_divisor": 1.2033, "padding_alignment": 128, "num_experts": 20, @@ -76,30 +244,48 @@ # ──────────────────────────── 正则 ──────────────────────────── -NUM_RE = re.compile(r'-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?') -DTYPE_RE = re.compile(r'"[^"]*"|Dtype\([^)]*\)') # 保护区:内部数字不提取 -INT_RE = re.compile(r'-?\d+') +NUM_RE = re.compile(r"-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?") +DTYPE_RE = re.compile(r'"[^"]*"|Dtype\([^)]*\)') # 保护区:内部数字不提取 +INT_RE = re.compile(r"-?\d+") # MoE 解析 MOE_PERMUTE_RE = re.compile( - r'moe_permute\(Tensor\(paddle\.Size\(\[(\d+), 7168\]\).*?' - r'tokens_per_expert=list\[([^\]]*)\], padding_alignment=128' + r"moe_permute\(Tensor\(paddle\.Size\(\[(\d+), 7168\]\).*?" + r"tokens_per_expert=list\[([^\]]*)\], padding_alignment=128" ) -SLICE_RE = re.compile(r'slice\((\d+),(\d+),None\)') +SLICE_RE = re.compile(r"slice\((\d+),(\d+),None\)") # 模型结构常量:永不作为 MoE block 标量映射的 key(即使某 block 的 # old_padded_N / old_input_N 偶然等于其中之一)。否则会把 Size([N,7168]) 里的 # hidden=7168 等常量误当作 buffer 维度放大。56=top_k*hidden/512、28、3584=hidden/2、 # 20=num_experts、4=top_k、143360/1120 等也是固定结构维度。 -MODEL_CONSTANTS = frozenset({ - 7168, 3584, 1792, 56, 28, 14, 20, 4, 8, 16, 32, 64, 128, - 143360, 1120, 256, 512, 1024, -}) +MODEL_CONSTANTS = frozenset( + { + 7168, + 3584, + 1792, + 56, + 28, + 14, + 20, + 4, + 8, + 16, + 32, + 64, + 128, + 143360, + 1120, + 256, + 512, + 1024, + } +) def signature(line): """结构签名:把所有数字替换为 #。""" - return NUM_RE.sub('#', line) + return NUM_RE.sub("#", line) def numbers(line): @@ -118,6 +304,7 @@ def is_int(text): # ──────────────────────────── 学习仿射映射(确定类) ──────────────────────────── + def collect_position_values(lines): """收集每个 (signature, position) 上出现过的所有整数。""" table = defaultdict(lambda: defaultdict(list)) @@ -140,18 +327,18 @@ def build_value_map(lines_small, lines_large): smalls = small.get(sig, {}).get(pos, []) cnt_s, cnt_l = Counter(smalls), Counter(larges) if cnt_s == cnt_l: - continue # 整列常量,跳过 + continue # 整列常量,跳过 common = cnt_s & cnt_l rem_s = sorted((cnt_s - common).elements()) rem_l = sorted((cnt_l - common).elements()) if len(rem_s) != len(rem_l) or not rem_l: - continue # 个数随 seq 变(多为 MoE),跳过 + continue # 个数随 seq 变(多为 MoE),跳过 candidates = defaultdict(set) for v_small, v_large in zip(rem_s, rem_l): v_target = v_small + (v_large - v_small) * MULT candidates[v_large].add(v_target) for v_large, targets in candidates.items(): - if len(targets) == 1: # 无歧义才采用 + if len(targets) == 1: # 无歧义才采用 vmap[(sig, pos)][v_large] = next(iter(targets)) return vmap @@ -179,6 +366,7 @@ def derive_line(line, vmap): # ──────────────────────────── MoE 结构重建 ──────────────────────────── # 见文件头 B 部分说明。 + def _proportional_int_split(total, weights, rng, jitter): """把整数 total 按 weights(占比,和为1)拆成 len(weights) 个非负整数, 施加 ±jitter 抖动后用最大余数法保证求和精确等于 total。""" @@ -216,7 +404,7 @@ def build_moe_plan(profile): pad = profile["padding_alignment"] n_calls = profile["moe_permute_calls"] - grand_total = n_calls * SEQ_TARGET * 4 # Σ over all calls 的 token 预算 + grand_total = n_calls * SEQ_TARGET * 4 # Σ over all calls 的 token 预算 # 各调用的 Σtpe(按真实占比 + 抖动,整数,和为 grand_total) call_sums = _proportional_int_split(grand_total, per_call, rng, JITTER) @@ -236,13 +424,15 @@ def build_moe_plan(profile): padded_each = [math.ceil(t / pad) * pad for t in tpe] padded_N = sum(padded_each) input_N = int(round(csum / divisor)) - plans.append({ - "tpe": tpe, - "sum_tpe": sum(tpe), - "padded_each": padded_each, - "padded_N": padded_N, - "input_N": input_N, - }) + plans.append( + { + "tpe": tpe, + "sum_tpe": sum(tpe), + "padded_each": padded_each, + "padded_N": padded_N, + "input_N": input_N, + } + ) return plans @@ -253,8 +443,8 @@ def _moe_block_bounds(lines): unpermute 行本身归入该 block 末尾。返回 [(p_idx, u_idx), ...]。 permute 有 fp8 / bf16 两种变体,各 144 个;每个 permute 都配一个 unpermute。 """ - perm_idx = [i for i, l in enumerate(lines) if 'functional.moe_permute' in l] - unperm_idx = [i for i, l in enumerate(lines) if 'functional.moe_unpermute' in l] + perm_idx = [i for i, l in enumerate(lines) if "functional.moe_permute" in l] + unperm_idx = [i for i, l in enumerate(lines) if "functional.moe_unpermute" in l] blocks = [] used = set() for p in perm_idx: @@ -270,16 +460,18 @@ def _moe_block_bounds(lines): def _parse_permute(line): """解析 moe_permute 行,返回 (variant, input_N, tpe_list) 或 None。""" - m = MOE_PERMUTE_RE.search(line) # fp8 变体(带 tokens_per_expert=) + m = MOE_PERMUTE_RE.search(line) # fp8 变体(带 tokens_per_expert=) if m: - tpe = [int(x) for x in m.group(2).split(',') if x.strip()] - return ('fp8', int(m.group(1)), tpe) + tpe = [int(x) for x in m.group(2).split(",") if x.strip()] + return ("fp8", int(m.group(1)), tpe) m = re.search( r'moe_permute\(Tensor\(paddle\.Size\(\[(\d+), 7168\]\),"bfloat16"\), ' - r'None,.*?, 20, list\[([^\]]*)\], padding_alignment=128', line) + r"None,.*?, 20, list\[([^\]]*)\], padding_alignment=128", + line, + ) if m: - tpe = [int(x) for x in m.group(2).split(',') if x.strip()] - return ('bf16', int(m.group(1)), tpe) + tpe = [int(x) for x in m.group(2).split(",") if x.strip()] + return ("bf16", int(m.group(1)), tpe) return None @@ -302,24 +494,27 @@ def _rewrite_int_run(line, old_to_new): def _rewrite_permute_line(line, variant, new_input_N, new_tpe): """重写 moe_permute 行:替换 input_N 与 tokens_per_expert 列表。""" - new_list = ','.join(str(t) for t in new_tpe) + ',' - if variant == 'fp8': + new_list = ",".join(str(t) for t in new_tpe) + "," + if variant == "fp8": line = MOE_PERMUTE_RE.sub( - lambda m: m.group(0).replace( - f'Size([{m.group(1)}, 7168]', f'Size([{new_input_N}, 7168]' - ).replace( - f'tokens_per_expert=list[{m.group(2)}]', - f'tokens_per_expert=list[{new_list}]' + lambda m: m.group(0) + .replace(f"Size([{m.group(1)}, 7168]", f"Size([{new_input_N}, 7168]") + .replace( + f"tokens_per_expert=list[{m.group(2)}]", f"tokens_per_expert=list[{new_list}]" ), - line, count=1) + line, + count=1, + ) # input_N 还出现在同一行其它 Tensor 的第一维([N,56] [N,4]) m2 = _parse_permute(line) else: m = re.search( r'moe_permute\(Tensor\(paddle\.Size\(\[(\d+), 7168\]\),"bfloat16"\), ' - r'None,.*?, 20, list\[([^\]]*)\], padding_alignment=128', line) + r"None,.*?, 20, list\[([^\]]*)\], padding_alignment=128", + line, + ) old_in, old_list = m.group(1), m.group(2) - line = line.replace(f'list[{old_list}]', f'list[{new_list}]', 1) + line = line.replace(f"list[{old_list}]", f"list[{new_list}]", 1) return line @@ -345,7 +540,7 @@ def apply_moe_reconstruction(derived, skeleton, moe_plan, profile): 与逐 block 精确值仅差每调用方差,对合成配置的形状/量级保真度完全够用。 """ pad = profile["padding_alignment"] - r = SEQ_TARGET / SEQ_LARGE # 全局缩放比(buffer 维近似线性) + r = SEQ_TARGET / SEQ_LARGE # 全局缩放比(buffer 维近似线性) def scale128(x, mode="round"): """把 x 按比例 r 缩放并对齐到 128。mode: round/floor/ceil。""" @@ -366,7 +561,7 @@ def scale128(x, mode="round"): global MOE_PADDED_N, MOE_PADDED_N_RAW MOE_PADDED_N_RAW = set() for l in skeleton: - if 'moe_permute' not in l: + if "moe_permute" not in l: continue pp = _parse_permute(l) if pp: @@ -393,11 +588,14 @@ def scale128(x, mode="round"): tpe[tpe.index(max(tpe))] += drift rng2.shuffle(tpe) padded_each = [math.ceil(t / pad) * pad for t in tpe] - bf_plan.append({ - "tpe": tpe, "padded_each": padded_each, - "padded_N": sum(padded_each), - "input_N": int(round(csum / divisor)), - }) + bf_plan.append( + { + "tpe": tpe, + "padded_each": padded_each, + "padded_N": sum(padded_each), + "input_N": int(round(csum / divisor)), + } + ) fp8_i = bf16_i = 0 changed = 0 @@ -406,14 +604,15 @@ def scale128(x, mode="round"): if parsed is None: continue variant, old_input_N, old_tpe = parsed - if variant == 'fp8': - plan = moe_plan[fp8_i % len(moe_plan)]; fp8_i += 1 + if variant == "fp8": + plan = moe_plan[fp8_i % len(moe_plan)] + fp8_i += 1 else: - plan = bf_plan[bf16_i % len(bf_plan)]; bf16_i += 1 + plan = bf_plan[bf16_i % len(bf_plan)] + bf16_i += 1 # permute 行里 input_N 出现在多个 Tensor 首维([N,7168][N,56][N,4])。 # 用 scalar_map 只替换 == old_input_N 的 token;7168 等常量绝不入表。 - line = _rewrite_permute_line(skeleton[p_idx], variant, - plan["input_N"], plan["tpe"]) + line = _rewrite_permute_line(skeleton[p_idx], variant, plan["input_N"], plan["tpe"]) sm = {} if old_input_N != plan["input_N"] and old_input_N not in MODEL_CONSTANTS: sm[old_input_N] = plan["input_N"] @@ -431,25 +630,51 @@ def scale128(x, mode="round"): # 携带 padded-buffer 维(数据相关、随 seq 线性增长)的算子。其首维 N 与 slice 边界 # 需随 seq 缩放;moe_permute / moe_unpermute 已在第 1 类单独精确处理,这里跳过。 # 缓冲维有两种书写:① Tensor(Size([N, DIM])) ② list[N, DIM,](empty/reshape/slice 的形状参数)。 -_BUFFER_SHAPE = re.compile(r'Size\(\[(\d+), (7168|3584|56|28)\]') +_BUFFER_SHAPE = re.compile(r"Size\(\[(\d+), (7168|3584|56|28)\]") # list[N,DIM,] 形式:DIM 为 MoE 维时 N=buffer。注意只匹配「DIM 在第二位」的二维形状, # 避免误伤 list[20,56,56,] 这类常量三维形状(其首维 20 是 num_experts 常量)。 -_BUFFER_LIST = re.compile(r'list\[(\d+),(7168|3584|56|28),\]') +_BUFFER_LIST = re.compile(r"list\[(\d+),(7168|3584|56|28),\]") _BUFFER_HEADS = ( - 'paddle.Tensor.__getitem__', 'paddle.Tensor.zero_', 'paddle.assign', - 'paddle.slice', 'paddle.empty_like', 'paddle.Tensor.reshape', - 'paddle.incubate.nn.functional.fused_linear', 'paddle.zeros_like', - 'paddle.Tensor.cast', 'paddle.incubate.nn.functional.fp8_quant_blockwise', - 'paddle._C_ops._run_custom_op', 'paddle.transpose', 'paddle.Tensor.clone', - 'paddle.Tensor.detach', 'paddle.reshape', 'paddle.nn.functional.linear', - 'paddle.incubate.nn.functional.fused_act_dequant', 'paddle.empty', - 'paddle.Tensor.unsqueeze', 'paddle.Tensor.contiguous', + "paddle.Tensor.__getitem__", + "paddle.Tensor.zero_", + "paddle.assign", + "paddle.slice", + "paddle.empty_like", + "paddle.Tensor.reshape", + "paddle.incubate.nn.functional.fused_linear", + "paddle.zeros_like", + "paddle.Tensor.cast", + "paddle.incubate.nn.functional.fp8_quant_blockwise", + "paddle._C_ops._run_custom_op", + "paddle.transpose", + "paddle.Tensor.clone", + "paddle.Tensor.detach", + "paddle.reshape", + "paddle.nn.functional.linear", + "paddle.incubate.nn.functional.fused_act_dequant", + "paddle.empty", + "paddle.Tensor.unsqueeze", + "paddle.Tensor.contiguous", ) # 首维为这些值的是结构常量张量([20,7168,7168]、[1120,56]、[3584,7168] 等),首维不缩放。 # 3584=hidden/2、7168=hidden、102400=vocab 分片、14336=2·hidden、64/192/2056 等是固定结构维。 -_CONST_FIRST_DIM = frozenset({ - 20, 1120, 143360, 3584, 7168, 14336, 102400, 64, 192, 160, 256, 512, 2056, -}) +_CONST_FIRST_DIM = frozenset( + { + 20, + 1120, + 143360, + 3584, + 7168, + 14336, + 102400, + 64, + 192, + 160, + 256, + 512, + 2056, + } +) def _scale_moe_buffer_lines(derived, skeleton, scale128, pad): @@ -460,7 +685,7 @@ def _scale_moe_buffer_lines(derived, skeleton, scale128, pad): 从而不碰常量张量的首维(如 [3584,7168]、[102400,7168])。""" changed = 0 for i, base in enumerate(skeleton): - head = base.split('(', 1)[0] + head = base.split("(", 1)[0] if head not in _BUFFER_HEADS: continue sm = _BUFFER_SHAPE.search(base) @@ -482,12 +707,14 @@ def _scale_moe_buffer_lines(derived, skeleton, scale128, pad): nb = min(scale128(b, "ceil"), new_N) if na > nb: na = nb - new_line = (new_line[:sl.start()] - + f'slice({na},{nb},None)' + new_line[sl.end():]) + new_line = ( + new_line[: sl.start()] + f"slice({na},{nb},None)" + new_line[sl.end() :] + ) new_line = _BUFFER_SHAPE.sub( - lambda m: m.group(0).replace( - f'[{m.group(1)}, ', f'[{new_N}, ', 1), - new_line, count=1) + lambda m: m.group(0).replace(f"[{m.group(1)}, ", f"[{new_N}, ", 1), + new_line, + count=1, + ) # ② list[N,DIM,]:empty/reshape/slice 的形状参数(N=行数, DIM∈MoE 维)。 # 歧义点:N==7168 时既可能是 padded buffer(empty 的行数),也可能是权重 reshape # 的行数(list[7168,7168,] 来自 flat[51380224]=7168² 的方阵权重)。靠算子区分: @@ -495,11 +722,12 @@ def _scale_moe_buffer_lines(derived, skeleton, scale128, pad): # - reshape/slice/其它形状参数 → 可能是权重 → 用剔除常量的 MOE_PADDED_N 集合 if lm: old_LN = int(lm.group(1)) - allow = MOE_PADDED_N_RAW if head == 'paddle.empty' else MOE_PADDED_N + allow = MOE_PADDED_N_RAW if head == "paddle.empty" else MOE_PADDED_N if old_LN in allow: nLN = scale128(old_LN, "ceil") new_line = _BUFFER_LIST.sub( - lambda m: f'list[{nLN},{m.group(2)},]', new_line, count=1) + lambda m: f"list[{nLN},{m.group(2)},]", new_line, count=1 + ) if new_line != base: derived[i] = new_line changed += 1 @@ -514,52 +742,77 @@ def _scale_moe_buffer_lines(derived, skeleton, scale128, pad): r'(moe_unpermute\(Tensor\(paddle\.Size\(\[)(\d+)(, 7168\]\),"bfloat16"\), ' r'Tensor\(paddle\.Size\(\[)(\d+)(, 20\]\),"int32"\), ' r'Tensor\(paddle\.Size\(\[)(\d+)(, 4\]\),"int32"\), ' - r'Tensor\(paddle\.Size\(\[)(\d+)(\]\),"float32"\), )(\d+)(, 20, \))') + r'Tensor\(paddle\.Size\(\[)(\d+)(\]\),"float32"\), )(\d+)(, 20, \))' +) def _scale_unpermute_lines(derived, skeleton, scale128): """缩放 moe_unpermute 行的 padded_N(首维与第4个张量)与 input_N(第2/3张量及尾参)。""" changed = 0 for i, base in enumerate(skeleton): - if 'moe_unpermute' not in base: + if "moe_unpermute" not in base: continue m = _UNPERM_RE.search(base) if not m: continue - old_pad = int(m.group(2)) # padded_N - old_in = int(m.group(4)) # input_N + old_pad = int(m.group(2)) # padded_N + old_in = int(m.group(4)) # input_N new_pad = scale128(old_pad, "ceil") new_in = scale128(old_in, "round") new_line = _UNPERM_RE.sub( - lambda mm: (mm.group(1) + str(new_pad) + mm.group(3) + str(new_in) - + mm.group(5) + str(new_in) + mm.group(7) + str(new_pad) - + mm.group(9) + str(new_in) + mm.group(11)), - base, count=1) + lambda mm: ( + mm.group(1) + + str(new_pad) + + mm.group(3) + + str(new_in) + + mm.group(5) + + str(new_in) + + mm.group(7) + + str(new_pad) + + mm.group(9) + + str(new_in) + + mm.group(11) + ), + base, + count=1, + ) if new_line != base: derived[i] = new_line changed += 1 return changed + def parse_args(): parser = argparse.ArgumentParser( description="API config 推导脚本:由两个不同 seq 的配置推导任意目标 seq(含 MoE 结构重建)。", ) - parser.add_argument("target_seq", type=int, - help="目标 sequence 长度(必须是 seq_small 的整数倍)") - parser.add_argument("--small", default=None, - help="小 seq 配置文件路径(默认:脚本目录下 api_config_.txt)") - parser.add_argument("--large", default=None, - help="大 seq 配置文件路径(默认:脚本目录下 api_config_.txt)") - parser.add_argument("--seq-small", type=int, default=1024, - help="小配置的 seq 值(默认:1024)") - parser.add_argument("--seq-large", type=int, default=2048, - help="大配置的 seq 值(默认:2048)") - parser.add_argument("-o", "--output", default=None, - help="输出文件路径(默认:源文件同目录下 api_config_derived_.txt)") - parser.add_argument("--seed", type=int, default=20240528, - help="MoE 重建随机种子(默认:20240528)") - parser.add_argument("--jitter", type=float, default=0.04, - help="MoE 重采样抖动幅度(默认:0.04)") + parser.add_argument( + "target_seq", type=int, help="目标 sequence 长度(必须是 seq_small 的整数倍)" + ) + parser.add_argument( + "--small", + default=None, + help="小 seq 配置文件路径(默认:脚本目录下 api_config_.txt)", + ) + parser.add_argument( + "--large", + default=None, + help="大 seq 配置文件路径(默认:脚本目录下 api_config_.txt)", + ) + parser.add_argument("--seq-small", type=int, default=1024, help="小配置的 seq 值(默认:1024)") + parser.add_argument("--seq-large", type=int, default=2048, help="大配置的 seq 值(默认:2048)") + parser.add_argument( + "-o", + "--output", + default=None, + help="输出文件路径(默认:源文件同目录下 api_config_derived_.txt)", + ) + parser.add_argument( + "--seed", type=int, default=20240528, help="MoE 重建随机种子(默认:20240528)" + ) + parser.add_argument( + "--jitter", type=float, default=0.04, help="MoE 重采样抖动幅度(默认:0.04)" + ) return parser.parse_args() @@ -574,8 +827,9 @@ def main(): RANDOM_SEED = args.seed JITTER = args.jitter - assert (SEQ_TARGET - SEQ_SMALL) % SEQ_SMALL == 0, \ + assert (SEQ_TARGET - SEQ_SMALL) % SEQ_SMALL == 0, ( f"TARGET ({SEQ_TARGET}) 必须满足 (TARGET - {SEQ_SMALL}) % {SEQ_SMALL} == 0" + ) MULT = (SEQ_TARGET - SEQ_SMALL) // SEQ_SMALL file_small = args.small or os.path.join(BASE_DIR, f"api_config_{SEQ_SMALL}.txt") @@ -598,12 +852,14 @@ def main(): print(f"错误:源文件缺失 {file_large}") sys.exit(1) - print(f"推导: seq {SEQ_SMALL}+{SEQ_LARGE} → {SEQ_TARGET} " - f"(确定类仿射 mult={MULT}; MoE 约束重采样, seed={RANDOM_SEED})") + print( + f"推导: seq {SEQ_SMALL}+{SEQ_LARGE} → {SEQ_TARGET} " + f"(确定类仿射 mult={MULT}; MoE 约束重采样, seed={RANDOM_SEED})" + ) print("=" * 70) - lines_small = [l.rstrip('\n') for l in open(file_small)] - lines_large = [l.rstrip('\n') for l in open(file_large)] + lines_small = [l.rstrip("\n") for l in open(file_small)] + lines_large = [l.rstrip("\n") for l in open(file_large)] print(f" seq={SEQ_SMALL}: {len(lines_small)} 行") print(f" seq={SEQ_LARGE}: {len(lines_large)} 行 (作为输出骨架)") @@ -613,8 +869,10 @@ def main(): profile = load_profile() moe_plan = build_moe_plan(profile) - print(f" MoE 重建计划: {len(moe_plan)} 个 moe_permute 调用, " - f"总 token 预算 {profile['moe_permute_calls'] * SEQ_TARGET * 4}") + print( + f" MoE 重建计划: {len(moe_plan)} 个 moe_permute 调用, " + f"总 token 预算 {profile['moe_permute_calls'] * SEQ_TARGET * 4}" + ) # 第一遍:确定类仿射改写(MoE block 内的行随后被结构重建覆盖) derived = [derive_line(l, vmap) for l in lines_large] @@ -624,8 +882,8 @@ def main(): affine_changed = sum(1 for a, b in zip(derived, lines_large) if a != b) - with open(output_path, 'w') as f: - f.write('\n'.join(derived) + '\n') + with open(output_path, "w") as f: + f.write("\n".join(derived) + "\n") print("=" * 70) print(f"完成: 共 {len(derived)} 行") diff --git a/tools/qa_test/extract_api_set.py b/tools/qa_test/extract_api_set.py index baa0a1a1..32e92e03 100644 --- a/tools/qa_test/extract_api_set.py +++ b/tools/qa_test/extract_api_set.py @@ -79,13 +79,15 @@ def parse_args(argv=None): """, ) parser.add_argument( - "-i", "--input", + "-i", + "--input", nargs="+", required=True, help="输入路径列表(支持文件或目录)", ) parser.add_argument( - "-o", "--output", + "-o", + "--output", default="api_extracted.txt", help="输出文件路径(默认:当前目录下 api_extracted.txt)", ) diff --git a/tools/qa_test/merge_configs.py b/tools/qa_test/merge_configs.py index 78e49e75..5a0d9aa7 100644 --- a/tools/qa_test/merge_configs.py +++ b/tools/qa_test/merge_configs.py @@ -6,6 +6,9 @@ python merge_configs.py -i file1.txt file2.txt file3.txt python merge_configs.py -i dir1/ file2.txt -o /output/merged.txt """ + +from __future__ import annotations + import argparse import glob import os @@ -15,10 +18,12 @@ def parse_args(): parser = argparse.ArgumentParser( description="合并多个 txt 配置文件,剔除空行。", ) - parser.add_argument("-i", "--inputs", nargs="+", required=True, - help="输入路径(目录或文件),可指定多个") - parser.add_argument("-o", "--output", default=None, - help="输出文件路径。默认:第一个输入目录下 merged.txt") + parser.add_argument( + "-i", "--inputs", nargs="+", required=True, help="输入路径(目录或文件),可指定多个" + ) + parser.add_argument( + "-o", "--output", default=None, help="输出文件路径。默认:第一个输入目录下 merged.txt" + ) return parser.parse_args() @@ -58,7 +63,7 @@ def main(): for filepath in txt_files: if os.path.abspath(filepath) == output_abs: continue - with open(filepath, "r") as f: + with open(filepath) as f: for line in f: stripped = line.strip() if stripped: diff --git a/tools/qa_test/to_0_size_config.py b/tools/qa_test/to_0_size_config.py index 8ba4241e..d651eb3c 100644 --- a/tools/qa_test/to_0_size_config.py +++ b/tools/qa_test/to_0_size_config.py @@ -162,7 +162,7 @@ def dump_item_str(item): elif isinstance(item, type): return "type(" + str(item)[str(item).index("'") + 1 : str(item).rindex("'")] + ")" elif callable(item): - name = getattr(item, '__name__', None) or getattr(item, '__qualname__', None) + name = getattr(item, "__name__", None) or getattr(item, "__qualname__", None) if name: return "callable(" + name + ")" return "callable(unknown)" @@ -281,10 +281,15 @@ def parse_args(): parser = argparse.ArgumentParser( description="将 API 配置转换为 0-size tensor 变体,用于边界测试。", ) - parser.add_argument("-i", "--inputs", nargs="+", required=True, - help="输入配置文件路径(可指定多个)") - parser.add_argument("-o", "--output", default="api_config_0_size.txt", - help="输出文件路径(默认:当前目录下 api_config_0_size.txt)") + parser.add_argument( + "-i", "--inputs", nargs="+", required=True, help="输入配置文件路径(可指定多个)" + ) + parser.add_argument( + "-o", + "--output", + default="api_config_0_size.txt", + help="输出文件路径(默认:当前目录下 api_config_0_size.txt)", + ) return parser.parse_args() @@ -307,4 +312,4 @@ def parse_args(): with open(args.output, "w") as f: f.write("\n".join(config_0_size)) - print(f"输出: {args.output},共 {len(config_0_size)} 行") \ No newline at end of file + print(f"输出: {args.output},共 {len(config_0_size)} 行") diff --git a/tools/qa_test/verify_api_seq.py b/tools/qa_test/verify_api_seq.py index b70cb4bf..1b26c709 100644 --- a/tools/qa_test/verify_api_seq.py +++ b/tools/qa_test/verify_api_seq.py @@ -19,29 +19,35 @@ 并打印 Top 不匹配行,便于定位推导脚本的薄弱点。 """ +from __future__ import annotations + import argparse import os import sys from collections import Counter - # 判定「数据相关、不可确定性推导」的行:MoE 相关算子 + 张量取片 MOE_MARKERS = ( - 'moe_permute', 'moe_unpermute', 'fp8_quant', - 'fused_act_dequant', 'fused_linear', '_run_custom_op', 'swiglu', + "moe_permute", + "moe_unpermute", + "fp8_quant", + "fused_act_dequant", + "fused_linear", + "_run_custom_op", + "swiglu", ) def is_deterministic(line): """确定类行 = 非 MoE 算子 且 非 __getitem__ 取片。""" - if line.startswith('paddle.Tensor.__getitem__'): + if line.startswith("paddle.Tensor.__getitem__"): return False return not any(m in line for m in MOE_MARKERS) def load(path): with open(path) as f: - return [l.rstrip('\n') for l in f] + return [l.rstrip("\n") for l in f] def overlap(counter_a, counter_b): @@ -54,8 +60,10 @@ def report(name, derived_lines, real_lines): inter = overlap(cd, cr) denom = len(real_lines) pct = inter * 100 / denom if denom else 0.0 - print(f" {name:<14}: {inter}/{denom} = {pct:.2f}% " - f"(推导 {len(derived_lines)} 行 / 真实 {denom} 行)") + print( + f" {name:<14}: {inter}/{denom} = {pct:.2f}% " + f"(推导 {len(derived_lines)} 行 / 真实 {denom} 行)" + ) return cd, cr @@ -63,10 +71,8 @@ def parse_args(): parser = argparse.ArgumentParser( description="对比推导配置与真实配置,量化推导准确率(多重集重叠)。", ) - parser.add_argument("-d", "--derived", required=True, - help="推导的配置文件路径") - parser.add_argument("-r", "--real", required=True, - help="真实/基准配置文件路径") + parser.add_argument("-d", "--derived", required=True, help="推导的配置文件路径") + parser.add_argument("-r", "--real", required=True, help="真实/基准配置文件路径") return parser.parse_args() @@ -107,11 +113,11 @@ def main(): # 逐行位置对比(仅供参考,受 MoE 顺序随机影响) n = min(len(derived), len(real)) exact = sum(1 for i in range(n) if derived[i] == real[i]) - print(f"[逐行位置精确匹配(仅参考)]: {exact}/{n} = {exact*100/n:.2f}%") + print(f"[逐行位置精确匹配(仅参考)]: {exact}/{n} = {exact * 100 / n:.2f}%") # 确定类的薄弱点 - miss = cd_det - cr_det # 推导出了、真实没有(推多/推错) - extra = cr_det - cd_det # 真实有、推导漏了(推少/推错) + miss = cd_det - cr_det # 推导出了、真实没有(推多/推错) + extra = cr_det - cd_det # 真实有、推导漏了(推少/推错) if miss or extra: print("\n[确定类未匹配 Top(定位推导薄弱点)]") if miss: