@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 44ac25eae2
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 14, 05:30 UTC

[CI] [Doc]: Add GH Action for auto labeling issues with `rocm` tag (#20988)

Signed-off-by: vllmellm <vllm.ellm@embeddedllm.com> Co-authored-by: Cyrus Leung <tlleungac@connect.ust.hk>

vllmellm committed Aug 27, 2025 at 00:20 UTC 44ac25eae2cbbdc1cbcca423777107a5ca90a8f4
1 file changed +305
.github/workflows/issue_autolabel.yml new
+305
@@ -0,0 +1,305 @@
1 +name: Label issues based on keywords
2 +on:
3 + issues:
4 + types: [opened, edited, reopened]
5 +permissions:
6 + issues: write # needed so the workflow can add labels
7 + contents: read
8 +concurrency:
9 + group: issue-labeler-${{ github.event.issue.number }}
10 + cancel-in-progress: true
11 +jobs:
12 + add-labels:
13 + runs-on: ubuntu-latest
14 + steps:
15 + - name: Label issues based on keywords
16 + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
17 + with:
18 + script: |
19 + // Configuration: Add new labels and keywords here
20 + const labelConfig = {
21 + rocm: {
22 + // Keyword search - matches whole words only (with word boundaries)
23 + keywords: [
24 + {
25 + term: "composable kernel",
26 + searchIn: "both"
27 + },
28 + {
29 + term: "rccl",
30 + searchIn: "body" // only search in body
31 + },
32 + {
33 + term: "migraphx",
34 + searchIn: "title" // only search in title
35 + },
36 + {
37 + term: "hipgraph",
38 + searchIn: "both"
39 + },
40 + {
41 + term: "ROCm System Management Interface",
42 + searchIn: "body"
43 + },
44 + ],
45 +
46 + // Substring search - matches anywhere in text (partial matches)
47 + substrings: [
48 + {
49 + term: "VLLM_ROCM_",
50 + searchIn: "both"
51 + },
52 + {
53 + term: "rocm",
54 + searchIn: "title"
55 + },
56 + {
57 + term: "amd",
58 + searchIn: "title"
59 + },
60 + {
61 + term: "hip-",
62 + searchIn: "both"
63 + },
64 + {
65 + term: "gfx",
66 + searchIn: "both"
67 + },
68 + {
69 + term: "cdna",
70 + searchIn: "both"
71 + },
72 + {
73 + term: "rdna",
74 + searchIn: "both"
75 + },
76 + {
77 + term: "torch_hip",
78 + searchIn: "body" // only in body
79 + },
80 + {
81 + term: "_hip",
82 + searchIn: "both"
83 + },
84 + {
85 + term: "hip_",
86 + searchIn: "both"
87 + },
88 +
89 + // ROCm tools and libraries
90 + {
91 + term: "hipify",
92 + searchIn: "both"
93 + },
94 + ],
95 +
96 + // Regex patterns - for complex pattern matching
97 + regexPatterns: [
98 + {
99 + pattern: "\\bmi\\d{3}[a-z]*\\b",
100 + description: "AMD GPU names (mi + 3 digits + optional letters)",
101 + flags: "gi",
102 + searchIn: "both" // "title", "body", or "both"
103 + }
104 + ],
105 + },
106 + };
107 +
108 + // Helper function to create regex based on search type
109 + function createSearchRegex(term, type) {
110 + // Escape special regex characters in the term
111 + const escapedTerm = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
112 +
113 + switch (type) {
114 + case 'keyword':
115 + // Word boundary search - matches whole words only
116 + return new RegExp(`\\b${escapedTerm}\\b`, "gi");
117 + case 'substring':
118 + // Substring search - matches anywhere in the text
119 + return new RegExp(escapedTerm, "gi");
120 + default:
121 + throw new Error(`Unknown search type: ${type}`);
122 + }
123 + }
124 +
125 + // Helper function to find matching terms in text with line information
126 + function findMatchingTermsWithLines(text, searchTerms = [], searchType = 'keyword', searchLocation = '') {
127 + const matches = [];
128 + const lines = text.split('\n');
129 +
130 + for (const termConfig of searchTerms) {
131 + let regex;
132 + let term, searchIn, pattern, description, flags;
133 +
134 + // Handle different input formats (string or object)
135 + if (typeof termConfig === 'string') {
136 + term = termConfig;
137 + searchIn = 'both'; // default
138 + } else {
139 + term = termConfig.term;
140 + searchIn = termConfig.searchIn || 'both';
141 + pattern = termConfig.pattern;
142 + description = termConfig.description;
143 + flags = termConfig.flags;
144 + }
145 +
146 + // Skip if this term shouldn't be searched in the current location
147 + if (searchIn !== 'both' && searchIn !== searchLocation) {
148 + continue;
149 + }
150 +
151 + // Create appropriate regex
152 + if (searchType === 'regex') {
153 + regex = new RegExp(pattern, flags || "gi");
154 + } else {
155 + regex = createSearchRegex(term, searchType);
156 + }
157 +
158 + const termMatches = [];
159 +
160 + // Check each line for matches
161 + lines.forEach((line, lineIndex) => {
162 + const lineMatches = line.match(regex);
163 + if (lineMatches) {
164 + lineMatches.forEach(match => {
165 + termMatches.push({
166 + match: match,
167 + lineNumber: lineIndex + 1,
168 + lineContent: line.trim(),
169 + searchType: searchType,
170 + searchLocation: searchLocation,
171 + originalTerm: term || pattern,
172 + description: description,
173 + // Show context around the match in the line
174 + context: line.length > 100 ?
175 + line.substring(Math.max(0, line.toLowerCase().indexOf(match.toLowerCase()) - 30),
176 + line.toLowerCase().indexOf(match.toLowerCase()) + match.length + 30) + '...'
177 + : line.trim()
178 + });
179 + });
180 + }
181 + });
182 +
183 + if (termMatches.length > 0) {
184 + matches.push({
185 + term: term || (description || pattern),
186 + searchType: searchType,
187 + searchLocation: searchLocation,
188 + searchIn: searchIn,
189 + pattern: pattern,
190 + matches: termMatches,
191 + count: termMatches.length
192 + });
193 + }
194 + }
195 +
196 + return matches;
197 + }
198 +
199 + // Helper function to check if label should be added
200 + async function processLabel(labelName, config) {
201 + const body = context.payload.issue.body || "";
202 + const title = context.payload.issue.title || "";
203 +
204 + core.notice(`Processing label: ${labelName}`);
205 + core.notice(`Issue Title: "${title}"`);
206 + core.notice(`Issue Body length: ${body.length} characters`);
207 +
208 + let shouldAddLabel = false;
209 + let allMatches = [];
210 + let reason = '';
211 +
212 + const keywords = config.keywords || [];
213 + const substrings = config.substrings || [];
214 + const regexPatterns = config.regexPatterns || [];
215 +
216 + core.notice(`Searching with ${keywords.length} keywords, ${substrings.length} substrings, and ${regexPatterns.length} regex patterns`);
217 +
218 + // Search in title
219 + if (title.trim()) {
220 + core.notice(`Searching in title: "${title}"`);
221 +
222 + const titleKeywordMatches = findMatchingTermsWithLines(title, keywords, 'keyword', 'title');
223 + const titleSubstringMatches = findMatchingTermsWithLines(title, substrings, 'substring', 'title');
224 + const titleRegexMatches = findMatchingTermsWithLines(title, regexPatterns, 'regex', 'title');
225 +
226 + allMatches.push(...titleKeywordMatches, ...titleSubstringMatches, ...titleRegexMatches);
227 + }
228 +
229 + // Search in body
230 + if (body.trim()) {
231 + core.notice(`Searching in body (${body.length} characters)`);
232 +
233 + const bodyKeywordMatches = findMatchingTermsWithLines(body, keywords, 'keyword', 'body');
234 + const bodySubstringMatches = findMatchingTermsWithLines(body, substrings, 'substring', 'body');
235 + const bodyRegexMatches = findMatchingTermsWithLines(body, regexPatterns, 'regex', 'body');
236 +
237 + allMatches.push(...bodyKeywordMatches, ...bodySubstringMatches, ...bodyRegexMatches);
238 + }
239 +
240 + if (allMatches.length > 0) {
241 + core.notice(`Found ${allMatches.length} matching term(s):`);
242 +
243 + for (const termMatch of allMatches) {
244 + const locationText = termMatch.searchLocation === 'title' ? 'title' : 'body';
245 + const searchInText = termMatch.searchIn === 'both' ? 'both' : termMatch.searchIn;
246 +
247 + if (termMatch.searchType === 'regex') {
248 + core.notice(` 📍 Regex: "${termMatch.term}" (pattern: ${termMatch.pattern}) found ${termMatch.count} time(s) in ${locationText} (configured to search in: ${searchInText}):`);
249 + } else {
250 + core.notice(` 📍 Term: "${termMatch.term}" (${termMatch.searchType} search) found ${termMatch.count} time(s) in ${locationText} (configured to search in: ${searchInText}):`);
251 + }
252 +
253 + // Show details for each match
254 + termMatch.matches.forEach((match, index) => {
255 + core.notice(` ${index + 1}. Line ${match.lineNumber} in ${match.searchLocation}: "${match.match}" [${match.searchType}]`);
256 + if (match.description) {
257 + core.notice(` Description: ${match.description}`);
258 + }
259 + core.notice(` Context: ${match.context}`);
260 + if (match.lineContent !== match.context) {
261 + core.notice(` Full line: ${match.lineContent}`);
262 + }
263 + });
264 + }
265 +
266 + shouldAddLabel = true;
267 + const totalMatches = allMatches.reduce((sum, t) => sum + t.count, 0);
268 + const titleMatches = allMatches.filter(t => t.searchLocation === 'title').reduce((sum, t) => sum + t.count, 0);
269 + const bodyMatches = allMatches.filter(t => t.searchLocation === 'body').reduce((sum, t) => sum + t.count, 0);
270 + const keywordMatches = allMatches.filter(t => t.searchType === 'keyword').reduce((sum, t) => sum + t.count, 0);
271 + const substringMatches = allMatches.filter(t => t.searchType === 'substring').reduce((sum, t) => sum + t.count, 0);
272 + const regexMatches = allMatches.filter(t => t.searchType === 'regex').reduce((sum, t) => sum + t.count, 0);
273 +
274 + reason = `Found ${totalMatches} total matches (${titleMatches} in title, ${bodyMatches} in body) - ${keywordMatches} keyword matches, ${substringMatches} substring matches, ${regexMatches} regex matches`;
275 + }
276 +
277 + core.notice(`Final decision: ${shouldAddLabel ? 'ADD LABEL' : 'DO NOT ADD LABEL'}`);
278 + core.notice(`Reason: ${reason || 'No matching terms found'}`);
279 +
280 + if (shouldAddLabel) {
281 + const existingLabels = context.payload.issue.labels.map(l => l.name);
282 + if (!existingLabels.includes(labelName)) {
283 + await github.rest.issues.addLabels({
284 + owner: context.repo.owner,
285 + repo: context.repo.repo,
286 + issue_number: context.issue.number,
287 + labels: [labelName],
288 + });
289 + core.notice(`Label "${labelName}" added. ${reason}`);
290 + return true;
291 + }
292 + core.notice(`Label "${labelName}" already present.`);
293 + return false;
294 + }
295 +
296 + core.notice(`No matching terms found for label "${labelName}".`);
297 + return false;
298 + }
299 +
300 + // Process all configured labels
301 + const processLabels = Object.entries(labelConfig)
302 + .map(([labelName, config]) => processLabel(labelName, config));
303 + const labelsAdded = await Promise.all(processLabels);
304 + const numLabelsAdded = labelsAdded.reduce((x, y) => x + y, 0);
305 + core.notice(`Processing complete. ${numLabelsAdded} label(s) added.`);
\ No newline at end of file