|
3 | 3 | #include <algorithm> |
4 | 4 | #include <cstdio> |
5 | 5 | #include <cstdlib> |
| 6 | +#include <cstring> |
6 | 7 | #include <fstream> |
7 | 8 | #include <numeric> |
8 | 9 |
|
@@ -118,6 +119,75 @@ std::vector<int> MoeHybridRoutingStats::hot_experts(int layer_idx, int hot_count |
118 | 119 | return ranked; |
119 | 120 | } |
120 | 121 |
|
| 122 | +void MoeHybridRoutingStats::print_freq_analysis() const { |
| 123 | + if (n_layer <= 0 || n_expert <= 0 || counts.empty()) return; |
| 124 | + |
| 125 | + uint64_t total_all = 0; |
| 126 | + for (int il = 0; il < n_layer; ++il) total_all += layer_totals[(size_t)il]; |
| 127 | + if (total_all == 0) return; |
| 128 | + |
| 129 | + // Estimate total tokens: layer_totals[0] / n_expert_used |
| 130 | + uint64_t total_tokens = (n_expert_used > 0 && layer_totals[0] > 0) |
| 131 | + ? layer_totals[0] / (uint64_t)n_expert_used : 0; |
| 132 | + double avg_k = (n_layer > 0 && total_tokens > 0) |
| 133 | + ? (double)total_all / ((double)n_layer * (double)total_tokens) : 0.0; |
| 134 | + |
| 135 | + std::fprintf(stderr, "\n=== Expert Frequency Analysis ===\n"); |
| 136 | + std::fprintf(stderr, "Tokens tracked: %llu, avg-K=%.2f\n\n", |
| 137 | + (unsigned long long)total_tokens, avg_k); |
| 138 | + |
| 139 | + int experts_for_80_total = 0; |
| 140 | + std::vector<uint64_t> sorted((size_t)n_expert); |
| 141 | + |
| 142 | + for (int il = 0; il < n_layer; ++il) { |
| 143 | + uint64_t tact = layer_totals[(size_t)il]; |
| 144 | + if (tact == 0) { |
| 145 | + std::fprintf(stderr, "Layer %2d: 0 unique\n", il); |
| 146 | + continue; |
| 147 | + } |
| 148 | + for (int ie = 0; ie < n_expert; ++ie) |
| 149 | + sorted[(size_t)ie] = counts[index_of(il, ie)]; |
| 150 | + std::sort(sorted.begin(), sorted.end(), std::greater<uint64_t>()); |
| 151 | + |
| 152 | + int unique = 0; |
| 153 | + for (int ie = 0; ie < n_expert; ++ie) if (sorted[(size_t)ie] > 0) unique++; |
| 154 | + |
| 155 | + uint64_t cum = 0; |
| 156 | + uint64_t top10 = 0, top30 = 0, top60 = 0; |
| 157 | + int n50 = 0, n80 = 0, n90 = 0; |
| 158 | + for (int ie = 0; ie < n_expert; ++ie) { |
| 159 | + cum += sorted[(size_t)ie]; |
| 160 | + if (ie == 9) top10 = cum; |
| 161 | + if (ie == 29) top30 = cum; |
| 162 | + if (ie == 59) top60 = cum; |
| 163 | + if (!n50 && cum * 100 >= tact * 50) n50 = ie + 1; |
| 164 | + if (!n80 && cum * 100 >= tact * 80) n80 = ie + 1; |
| 165 | + if (!n90 && cum * 100 >= tact * 90) n90 = ie + 1; |
| 166 | + } |
| 167 | + // If there are fewer experts than a bucket size, the ie==N-1 check |
| 168 | + // never fires and topN stays 0, printing a bogus 0%. cum == tact here |
| 169 | + // (all experts summed), so the bucket covers everything → 100%. |
| 170 | + if (n_expert < 10) top10 = cum; |
| 171 | + if (n_expert < 30) top30 = cum; |
| 172 | + if (n_expert < 60) top60 = cum; |
| 173 | + std::fprintf(stderr, "Layer %2d: %3d unique, top-10 %.0f%%, top-30 %.0f%%, top-60 %.0f%% " |
| 174 | + "(50%%@%d, 80%%@%d, 90%%@%d)\n", |
| 175 | + il, unique, |
| 176 | + 100.0 * (double)top10 / (double)tact, |
| 177 | + 100.0 * (double)top30 / (double)tact, |
| 178 | + 100.0 * (double)top60 / (double)tact, |
| 179 | + n50, n80, n90); |
| 180 | + experts_for_80_total += n80; |
| 181 | + } |
| 182 | + |
| 183 | + double avg80 = (n_layer > 0) ? (double)experts_for_80_total / n_layer : 0.0; |
| 184 | + std::fprintf(stderr, "\n--- Overall Summary ---\n"); |
| 185 | + std::fprintf(stderr, "80%% hit rate: %d experts pinned (avg %.0f/layer)\n", |
| 186 | + experts_for_80_total, avg80); |
| 187 | + std::fprintf(stderr, "Model: %d layers x %d experts, top-%d routing\n", |
| 188 | + n_layer, n_expert, n_expert_used); |
| 189 | +} |
| 190 | + |
121 | 191 | bool MoeHybridRoutingStats::save_csv(const std::string & path, std::string * err) const { |
122 | 192 | if (n_layer <= 0 || n_expert <= 0 || counts.size() != (size_t)n_layer * (size_t)n_expert) { |
123 | 193 | if (err) *err = "routing stats not initialized"; |
|
0 commit comments