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] [ROCm] Automate CC list for ROCm related issue (#26753)
Signed-off-by: vllmellm <vllm.ellm@embeddedllm.com>
vllmellm committed
Oct 14, 2025 at 13:57 UTC
4821ac1b4d36c5780ec9769c40fcacc0f8c41a7d
1 file changed
+95
-43
.github/workflows/issue_autolabel.yml
+95
-43
@@ -13,6 +13,7 @@ jobs:
13
runs-on: ubuntu-latest
14
steps:
15
- name: Label issues based on keywords
16
+ id: label-step
17
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
18
with:
19
script: |
@@ -42,7 +43,6 @@ jobs:
43
searchIn: "body"
44
},
45
],
45
-
46
// Substring search - matches anywhere in text (partial matches)
47
substrings: [
48
{
@@ -89,14 +89,12 @@ jobs:
89
term: "hip_",
90
searchIn: "both"
91
},
92
-
92
// ROCm tools and libraries
93
{
94
term: "hipify",
95
searchIn: "both"
96
},
97
],
99
-
98
// Regex patterns - for complex pattern matching
99
regexPatterns: [
100
{
@@ -107,13 +105,17 @@ jobs:
105
}
106
],
107
},
108
+ // Add more label configurations here as needed
109
+ // example: {
110
+ // keywords: [...],
111
+ // substrings: [...],
112
+ // regexPatterns: [...]
113
+ // },
114
};
111
-
115
// Helper function to create regex based on search type
116
function createSearchRegex(term, type) {
117
// Escape special regex characters in the term
118
const escapedTerm = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
116
-
119
switch (type) {
120
case 'keyword':
121
// Word boundary search - matches whole words only
@@ -125,16 +127,13 @@ jobs:
127
throw new Error(`Unknown search type: ${type}`);
128
}
129
}
128
-
130
// Helper function to find matching terms in text with line information
131
function findMatchingTermsWithLines(text, searchTerms = [], searchType = 'keyword', searchLocation = '') {
132
const matches = [];
133
const lines = text.split('\n');
133
-
134
for (const termConfig of searchTerms) {
135
let regex;
136
let term, searchIn, pattern, description, flags;
137
-
137
// Handle different input formats (string or object)
138
if (typeof termConfig === 'string') {
139
term = termConfig;
@@ -146,21 +145,17 @@ jobs:
145
description = termConfig.description;
146
flags = termConfig.flags;
147
}
149
-
148
// Skip if this term shouldn't be searched in the current location
149
if (searchIn !== 'both' && searchIn !== searchLocation) {
150
continue;
151
}
154
-
152
// Create appropriate regex
153
if (searchType === 'regex') {
154
regex = new RegExp(pattern, flags || "gi");
155
} else {
156
regex = createSearchRegex(term, searchType);
157
}
161
-
158
const termMatches = [];
163
-
159
// Check each line for matches
160
lines.forEach((line, lineIndex) => {
161
const lineMatches = line.match(regex);
@@ -175,15 +170,14 @@ jobs:
170
originalTerm: term || pattern,
171
description: description,
172
// Show context around the match in the line
178
- context: line.length > 100 ?
179
- line.substring(Math.max(0, line.toLowerCase().indexOf(match.toLowerCase()) - 30),
180
- line.toLowerCase().indexOf(match.toLowerCase()) + match.length + 30) + '...'
173
+ context: line.length > 100 ?
174
+ line.substring(Math.max(0, line.toLowerCase().indexOf(match.toLowerCase()) - 30),
175
+ line.toLowerCase().indexOf(match.toLowerCase()) + match.length + 30) + '...'
176
: line.trim()
177
});
178
});
179
}
180
});
186
-
181
if (termMatches.length > 0) {
182
matches.push({
183
term: term || (description || pattern),
@@ -196,64 +190,48 @@ jobs:
190
});
191
}
192
}
199
-
193
return matches;
194
}
202
-
195
// Helper function to check if label should be added
196
async function processLabel(labelName, config) {
197
const body = context.payload.issue.body || "";
198
const title = context.payload.issue.title || "";
207
-
199
core.notice(`Processing label: ${labelName}`);
200
core.notice(`Issue Title: "${title}"`);
201
core.notice(`Issue Body length: ${body.length} characters`);
211
-
202
let shouldAddLabel = false;
203
let allMatches = [];
204
let reason = '';
215
-
205
const keywords = config.keywords || [];
206
const substrings = config.substrings || [];
207
const regexPatterns = config.regexPatterns || [];
219
-
208
core.notice(`Searching with ${keywords.length} keywords, ${substrings.length} substrings, and ${regexPatterns.length} regex patterns`);
221
-
209
// Search in title
210
if (title.trim()) {
211
core.notice(`Searching in title: "${title}"`);
225
-
212
const titleKeywordMatches = findMatchingTermsWithLines(title, keywords, 'keyword', 'title');
213
const titleSubstringMatches = findMatchingTermsWithLines(title, substrings, 'substring', 'title');
214
const titleRegexMatches = findMatchingTermsWithLines(title, regexPatterns, 'regex', 'title');
229
-
215
allMatches.push(...titleKeywordMatches, ...titleSubstringMatches, ...titleRegexMatches);
216
}
232
-
217
// Search in body
218
if (body.trim()) {
219
core.notice(`Searching in body (${body.length} characters)`);
236
-
220
const bodyKeywordMatches = findMatchingTermsWithLines(body, keywords, 'keyword', 'body');
221
const bodySubstringMatches = findMatchingTermsWithLines(body, substrings, 'substring', 'body');
222
const bodyRegexMatches = findMatchingTermsWithLines(body, regexPatterns, 'regex', 'body');
240
-
223
allMatches.push(...bodyKeywordMatches, ...bodySubstringMatches, ...bodyRegexMatches);
224
}
243
-
225
if (allMatches.length > 0) {
226
core.notice(`Found ${allMatches.length} matching term(s):`);
246
-
227
for (const termMatch of allMatches) {
228
const locationText = termMatch.searchLocation === 'title' ? 'title' : 'body';
229
const searchInText = termMatch.searchIn === 'both' ? 'both' : termMatch.searchIn;
250
-
230
if (termMatch.searchType === 'regex') {
231
core.notice(` 📍 Regex: "${termMatch.term}" (pattern: ${termMatch.pattern}) found ${termMatch.count} time(s) in ${locationText} (configured to search in: ${searchInText}):`);
232
} else {
233
core.notice(` 📍 Term: "${termMatch.term}" (${termMatch.searchType} search) found ${termMatch.count} time(s) in ${locationText} (configured to search in: ${searchInText}):`);
234
}
256
-
235
// Show details for each match
236
termMatch.matches.forEach((match, index) => {
237
core.notice(` ${index + 1}. Line ${match.lineNumber} in ${match.searchLocation}: "${match.match}" [${match.searchType}]`);
@@ -266,7 +244,6 @@ jobs:
244
}
245
});
246
}
269
-
247
shouldAddLabel = true;
248
const totalMatches = allMatches.reduce((sum, t) => sum + t.count, 0);
249
const titleMatches = allMatches.filter(t => t.searchLocation === 'title').reduce((sum, t) => sum + t.count, 0);
@@ -274,13 +251,10 @@ jobs:
251
const keywordMatches = allMatches.filter(t => t.searchType === 'keyword').reduce((sum, t) => sum + t.count, 0);
252
const substringMatches = allMatches.filter(t => t.searchType === 'substring').reduce((sum, t) => sum + t.count, 0);
253
const regexMatches = allMatches.filter(t => t.searchType === 'regex').reduce((sum, t) => sum + t.count, 0);
277
-
254
reason = `Found ${totalMatches} total matches (${titleMatches} in title, ${bodyMatches} in body) - ${keywordMatches} keyword matches, ${substringMatches} substring matches, ${regexMatches} regex matches`;
255
}
280
-
256
core.notice(`Final decision: ${shouldAddLabel ? 'ADD LABEL' : 'DO NOT ADD LABEL'}`);
257
core.notice(`Reason: ${reason || 'No matching terms found'}`);
283
-
258
if (shouldAddLabel) {
259
const existingLabels = context.payload.issue.labels.map(l => l.name);
260
if (!existingLabels.includes(labelName)) {
@@ -296,14 +270,92 @@ jobs:
270
core.notice(`Label "${labelName}" already present.`);
271
return false;
272
}
299
-
273
core.notice(`No matching terms found for label "${labelName}".`);
274
return false;
275
}
303
-
276
// Process all configured labels
305
- const processLabels = Object.entries(labelConfig)
306
- .map(([labelName, config]) => processLabel(labelName, config));
307
- const labelsAdded = await Promise.all(processLabels);
308
- const numLabelsAdded = labelsAdded.reduce((x, y) => x + y, 0);
309
- core.notice(`Processing complete. ${numLabelsAdded} label(s) added.`);
\ No newline at end of file
277
+ const labelsAddedResults = await Promise.all(
278
+ Object.entries(labelConfig).map(([labelName, config]) =>
279
+ processLabel(labelName, config).then(added => ({ labelName, added }))
280
+ )
281
+ );
282
+
283
+ const numLabelsAdded = labelsAddedResults.filter(r => r.added).length;
284
+ core.notice(`Processing complete. ${numLabelsAdded} label(s) added.`);
285
+
286
+ // Return which labels were added for the next step
287
+ const addedLabels = labelsAddedResults.filter(r => r.added).map(r => r.labelName);
288
+ core.setOutput('labels_added', JSON.stringify(addedLabels));
289
+ return addedLabels;
290
+
291
+ - name: CC users for labeled issues
292
+ if: steps.label-step.outputs.labels_added != '[]'
293
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
294
+ with:
295
+ script: |
296
+ // Configuration: Map labels to GitHub users to CC
297
+ // You can add multiple users per label, and multiple label configurations
298
+ const ccConfig = {
299
+ rocm: {
300
+ users: ['hongxiayang', 'tjtanaa', 'vllmellm'], // Add more users as needed: ['user1', 'user2', 'user3']
301
+ message: 'CC {users} for ROCm-related issue' // {users} will be replaced with @mentions
302
+ },
303
+ // Add more label -> user mappings here
304
+ // Example:
305
+ // cuda: {
306
+ // users: ['user1', 'user2'],
307
+ // message: 'CC {users} for CUDA-related issue'
308
+ // },
309
+ // performance: {
310
+ // users: ['perfexpert'],
311
+ // message: 'CC {users} for performance issue'
312
+ // },
313
+ };
314
+
315
+ const labelsAdded = JSON.parse('${{ steps.label-step.outputs.labels_added }}');
316
+ core.notice(`Labels added: ${labelsAdded.join(', ')}`);
317
+
318
+ // Get existing comments to check for already mentioned users
319
+ const comments = await github.rest.issues.listComments({
320
+ owner: context.repo.owner,
321
+ repo: context.repo.repo,
322
+ issue_number: context.issue.number,
323
+ });
324
+
325
+ const issueBody = context.payload.issue.body || '';
326
+ const allExistingText = issueBody + '\n' + comments.data.map(c => c.body).join('\n');
327
+
328
+ // Process each label that was added
329
+ for (const label of labelsAdded) {
330
+ if (ccConfig[label]) {
331
+ const config = ccConfig[label];
332
+ const usersToMention = [];
333
+
334
+ // Check which users haven't been mentioned yet
335
+ for (const user of config.users) {
336
+ const mentionPattern = new RegExp(`@${user}\\b`, 'i');
337
+ if (!mentionPattern.test(allExistingText)) {
338
+ usersToMention.push(user);
339
+ } else {
340
+ core.notice(`@${user} already mentioned for label "${label}", skipping`);
341
+ }
342
+ }
343
+
344
+ // Post comment if there are users to mention
345
+ if (usersToMention.length > 0) {
346
+ const mentions = usersToMention.map(u => `@${u}`).join(' ');
347
+ const message = config.message.replace('{users}', mentions);
348
+
349
+ await github.rest.issues.createComment({
350
+ owner: context.repo.owner,
351
+ repo: context.repo.repo,
352
+ issue_number: context.issue.number,
353
+ body: message
354
+ });
355
+
356
+ core.notice(`CC comment added for label "${label}": ${mentions}`);
357
+ } else {
358
+ core.notice(`All users for label "${label}" already mentioned, skipping comment`);
359
+ }
360
+ }
361
+ }
\ No newline at end of file