@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 4821ac1b4d
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] [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