You can also use left_on=, right_on=, left_index= or right_index= parameters as well. The values are matched in the order the keys are passed in that case; the first key in left_on will be matched with the first key in right_on etc.
So using the example in the OP, the following two produce the same output:
a.merge(b, left_on=['A', 'B'], right_on=['A', 'B'])
a.merge(b, on=['A', 'B'])
However, a.merge(b, left_on=['A', 'B'], right_on=['B', 'A']) will produce a very different output because a['A'] is matched to b['B'] and a['B'] is matched to b['A'].
This is especially useful if the keys to match are named differently. For example:
a.merge(b, left_on=['A1', 'A2'], right_on=['B1', 'B2'])
This is equivalent to the SQL query:
SELECT * FROM a INNER JOIN b ON a.A1=b.B1 AND a.A2=b.B2
A useful note: Because reindexing occurs under the hood (source), the merged output is sorted by the values in the left keys (apparently that's not the case in cudf 24.02 but that's another matter).